diff --git a/.github/attention.md b/.github/attention.md new file mode 100644 index 000000000..868d585c0 --- /dev/null +++ b/.github/attention.md @@ -0,0 +1,5 @@ +Hey 👋 You DID in fact read my fancy PR description BEFORE you started reviewing the code, right? Else switch back to description now and read it in full: + +https://github.com/radixdlt/sargon/pull/131 + +Thank you! Happy review! 🙏 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3f26b7721..d613fa3ec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -122,7 +122,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | rustup target add aarch64-apple-darwin - sh ./scripts/ios/test.sh --codecov ${{ env.SWIFT_CODE_COV_REPORT_PATH }} + sh ./scripts/ios/test.sh --build --codecov ${{ env.SWIFT_CODE_COV_REPORT_PATH }} - name: Upload to CodeCov.io uses: RDXWorks-actions/codecov-action@main @@ -204,7 +204,7 @@ jobs: uses: RDXWorks-actions/cargo-install@main with: crate: cargo-tarpaulin - tag: 0.27.3 + tag: 0.30.0 locked: true - name: Code Coverage - Generate diff --git a/.gitignore b/.gitignore index 9c3201bf0..6b498e896 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ # And locally, when we run `./scripts/ios/build-sargon.sh` the file gets # updated to something runnable and we can run locally... So we never need # this file part of git history. -apple/Sources/UniFFI/Sargon.swift +apple/Sources/UniFFI/*.swift /target tarpaulin-report.html diff --git a/Cargo.lock b/Cargo.lock index 16d212855..97879c778 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -596,6 +596,28 @@ name = "clap_lex" version = "0.7.0" source = "git+https://github.com/clap-rs/clap/?rev=8a7a13a5618cfdc4ff328624a5266e7b4d88649a#8a7a13a5618cfdc4ff328624a5266e7b4d88649a" +[[package]] +name = "clients" +version = "1.1.0" +dependencies = [ + "actix-rt", + "async-trait", + "drivers", + "enum-iterator", + "gateway_models", + "log", + "pretty_assertions", + "pretty_env_logger", + "profile", + "reqwest", + "ret", + "sargoncommon", + "serde", + "serde_json 1.0.108", + "strum 0.26.1", + "uniffi", +] + [[package]] name = "colorchoice" version = "1.0.1" @@ -873,6 +895,26 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "drivers" +version = "1.1.0" +dependencies = [ + "actix-rt", + "async-trait", + "enum-iterator", + "log", + "pretty_assertions", + "pretty_env_logger", + "profile", + "reqwest", + "ret", + "sargoncommon", + "serde", + "serde_json 1.0.108", + "strum 0.26.1", + "uniffi", +] + [[package]] name = "dyn-clone" version = "1.0.17" @@ -1143,6 +1185,17 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "gateway_models" +version = "1.1.0" +dependencies = [ + "derive_more", + "ret", + "sargoncommon", + "serde", + "uniffi", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1245,6 +1298,23 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hd" +version = "1.1.0" +dependencies = [ + "bip39", + "derive_more", + "enum-iterator", + "iota-crypto", + "k256 0.13.3 (git+https://github.com/RustCrypto/elliptic-curves?rev=e158ce5cf0e9acee2fd76aff2a628334f5c771e5)", + "sargoncommon", + "serde", + "serde_json 1.0.108", + "serde_with 3.4.0", + "uniffi", + "zeroize 1.7.0 (git+https://github.com/RustCrypto/utils?rev=df6d2f48a5e8afe8eef04ba32e2af55bacb41375)", +] + [[package]] name = "heck" version = "0.4.1" @@ -1809,6 +1879,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oneshot-uniffi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c548d5c78976f6955d72d0ced18c48ca07030f7a1d4024529fedd7c1c01b29c" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -2020,6 +2096,35 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profile" +version = "1.1.0" +dependencies = [ + "actix-rt", + "aes-gcm", + "derive_more", + "enum-as-inner", + "enum-iterator", + "hd", + "hex 0.4.3 (git+https://github.com/KokaKiwi/rust-hex/?rev=b2b4370b5bf021b98ee7adc92233e8de3f2de792)", + "hkdf", + "k256 0.13.3 (git+https://github.com/RustCrypto/elliptic-curves?rev=e158ce5cf0e9acee2fd76aff2a628334f5c771e5)", + "paste 1.0.14", + "pretty_assertions", + "radix-common", + "radix-engine-interface", + "radix-rust", + "ret", + "sargoncommon", + "serde", + "serde_json 1.0.108", + "serde_repr", + "serde_with 3.4.0", + "strum 0.26.1", + "uniffi", + "zeroize 1.7.0 (git+https://github.com/RustCrypto/utils?rev=df6d2f48a5e8afe8eef04ba32e2af55bacb41375)", +] + [[package]] name = "pulldown-cmark" version = "0.9.6" @@ -2330,6 +2435,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a03d7e7816ade40c6ecedff7515198906d644527771bd8fb5a335094353a15a" +[[package]] +name = "radix_connect" +version = "1.1.0" +dependencies = [ + "actix-rt", + "clients", + "derive_more", + "paste 1.0.14", + "pretty_assertions", + "profile", + "sargoncommon", + "serde", + "serde_json 1.0.108", + "serde_with 3.4.0", + "uniffi", + "url", +] + [[package]] name = "rand" version = "0.7.3" @@ -2509,6 +2632,31 @@ dependencies = [ "winreg", ] +[[package]] +name = "ret" +version = "1.1.0" +dependencies = [ + "derive_more", + "enum-iterator", + "pretty_assertions", + "radix-common", + "radix-common-derive", + "radix-engine", + "radix-engine-interface", + "radix-engine-toolkit", + "radix-engine-toolkit-json", + "radix-rust", + "radix-transactions", + "rand 0.8.5", + "sargoncommon", + "sbor", + "serde", + "serde_json 1.0.108", + "serde_with 3.4.0", + "strum 0.26.1", + "uniffi", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -2586,16 +2734,28 @@ dependencies = [ [[package]] name = "sargon" -version = "0.7.18" +version = "1.1.0" +dependencies = [ + "actix-rt", + "clients", + "drivers", + "hd", + "log", + "pretty_assertions", + "profile", + "sargoncommon", + "transaction", + "uniffi", +] + +[[package]] +name = "sargoncommon" +version = "1.1.0" dependencies = [ "actix-rt", "aes-gcm", "assert-json-diff", "async-trait", - "bip39", - "camino 1.0.8", - "cargo_toml 0.15.3 (git+https://gitlab.com/lib.rs/cargo_toml?rev=e498c94fc42a660c1ca1a28999ce1d757cfe77fe)", - "clap 4.5.1", "delegate", "derive_more", "enum-as-inner", @@ -2619,7 +2779,6 @@ dependencies = [ "radix-rust", "radix-transactions", "rand 0.8.5", - "regex 1.9.3 (git+https://github.com/rust-lang/regex/?rev=72f889ef3cca59ebac6a026f3646e8d92f056d88)", "reqwest", "sbor", "serde", @@ -3450,6 +3609,30 @@ dependencies = [ "once_cell", ] +[[package]] +name = "transaction" +version = "1.1.0" +dependencies = [ + "cargo_toml 0.15.3 (git+https://gitlab.com/lib.rs/cargo_toml?rev=e498c94fc42a660c1ca1a28999ce1d757cfe77fe)", + "derive_more", + "log", + "pretty_assertions", + "pretty_env_logger", + "profile", + "radix-common", + "radix-engine", + "radix-engine-interface", + "radix-engine-toolkit", + "radix-rust", + "rand 0.8.5", + "ret", + "sargoncommon", + "sbor", + "serde", + "serde_json 1.0.108", + "uniffi", +] + [[package]] name = "triomphe" version = "0.1.11" @@ -3535,7 +3718,7 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "uniffi" version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" +source = "git+https://github.com/sajjon/uniffi-rs/?rev=d2886a5c836307e6b3f6f1a78e4b18b84eab3893#d2886a5c836307e6b3f6f1a78e4b18b84eab3893" dependencies = [ "anyhow", "camino 1.1.7", @@ -3546,10 +3729,21 @@ dependencies = [ "uniffi_macros", ] +[[package]] +name = "uniffi-bindgen" +version = "0.1.0" +dependencies = [ + "camino 1.0.8", + "clap 4.5.1", + "regex 1.9.3 (git+https://github.com/rust-lang/regex/?rev=72f889ef3cca59ebac6a026f3646e8d92f056d88)", + "thiserror 1.0.50", + "uniffi", +] + [[package]] name = "uniffi_bindgen" version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" +source = "git+https://github.com/sajjon/uniffi-rs/?rev=d2886a5c836307e6b3f6f1a78e4b18b84eab3893#d2886a5c836307e6b3f6f1a78e4b18b84eab3893" dependencies = [ "anyhow", "askama", @@ -3573,7 +3767,7 @@ dependencies = [ [[package]] name = "uniffi_build" version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" +source = "git+https://github.com/sajjon/uniffi-rs/?rev=d2886a5c836307e6b3f6f1a78e4b18b84eab3893#d2886a5c836307e6b3f6f1a78e4b18b84eab3893" dependencies = [ "anyhow", "camino 1.1.7", @@ -3583,7 +3777,7 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" +source = "git+https://github.com/sajjon/uniffi-rs/?rev=d2886a5c836307e6b3f6f1a78e4b18b84eab3893#d2886a5c836307e6b3f6f1a78e4b18b84eab3893" dependencies = [ "quote", "syn 2.0.63", @@ -3592,13 +3786,14 @@ dependencies = [ [[package]] name = "uniffi_core" version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" +source = "git+https://github.com/sajjon/uniffi-rs/?rev=d2886a5c836307e6b3f6f1a78e4b18b84eab3893#d2886a5c836307e6b3f6f1a78e4b18b84eab3893" dependencies = [ "anyhow", "bytes", "camino 1.1.7", "log", "once_cell", + "oneshot-uniffi", "paste 1.0.15", "static_assertions", ] @@ -3606,7 +3801,7 @@ dependencies = [ [[package]] name = "uniffi_macros" version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" +source = "git+https://github.com/sajjon/uniffi-rs/?rev=d2886a5c836307e6b3f6f1a78e4b18b84eab3893#d2886a5c836307e6b3f6f1a78e4b18b84eab3893" dependencies = [ "bincode", "camino 1.1.7", @@ -3623,7 +3818,7 @@ dependencies = [ [[package]] name = "uniffi_meta" version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" +source = "git+https://github.com/sajjon/uniffi-rs/?rev=d2886a5c836307e6b3f6f1a78e4b18b84eab3893#d2886a5c836307e6b3f6f1a78e4b18b84eab3893" dependencies = [ "anyhow", "bytes", @@ -3634,7 +3829,7 @@ dependencies = [ [[package]] name = "uniffi_testing" version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" +source = "git+https://github.com/sajjon/uniffi-rs/?rev=d2886a5c836307e6b3f6f1a78e4b18b84eab3893#d2886a5c836307e6b3f6f1a78e4b18b84eab3893" dependencies = [ "anyhow", "camino 1.1.7", @@ -3646,7 +3841,7 @@ dependencies = [ [[package]] name = "uniffi_udl" version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" +source = "git+https://github.com/sajjon/uniffi-rs/?rev=d2886a5c836307e6b3f6f1a78e4b18b84eab3893#d2886a5c836307e6b3f6f1a78e4b18b84eab3893" dependencies = [ "anyhow", "textwrap", @@ -3884,7 +4079,7 @@ dependencies = [ [[package]] name = "weedle2" version = "5.0.0" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" +source = "git+https://github.com/sajjon/uniffi-rs/?rev=d2886a5c836307e6b3f6f1a78e4b18b84eab3893#d2886a5c836307e6b3f6f1a78e4b18b84eab3893" dependencies = [ "nom", ] diff --git a/Cargo.toml b/Cargo.toml index e92b86de8..37ede0e9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,27 +1,20 @@ -[package] -name = "sargon" -version = "0.7.18" -edition = "2021" -build = "build.rs" - -[profile.release] -incremental = false -panic = 'unwind' -codegen-units = 1 - -[[test]] -name = "vectors" - -[lib] -crate-type = ["staticlib", "cdylib", "lib"] - -[[bin]] -name = "sargon-bindgen" -path = "src/bindgen/bin.rs" -required-features = ["build-binary"] - -[dependencies] - +[workspace] +resolver = "2" +members = [ + "crates/common", + "crates/drivers", + "crates/gateway_models", + "crates/hierarchical_deterministic", + "crates/profile", + "crates/radix_connect", + "crates/transaction", + "crates/ret", + "crates/sargon", + "crates/uniffi-bindgen", +] +default-members = ["crates/sargon"] + +[workspace.dependencies] # zeroize = "1.7.0" zeroize = { git = "https://github.com/RustCrypto/utils", rev = "df6d2f48a5e8afe8eef04ba32e2af55bacb41375", features = [ "zeroize_derive", @@ -80,7 +73,8 @@ radix-rust = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "397a ] } radix-engine = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "397a1bc0bb67429f1c82fbe0a62b3ac2f89a6fad" } radix-common = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "397a1bc0bb67429f1c82fbe0a62b3ac2f89a6fad", features = [ - "serde", "secp256k1_sign_and_validate" + "serde", + "secp256k1_sign_and_validate", ] } radix-common-derive = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "397a1bc0bb67429f1c82fbe0a62b3ac2f89a6fad" } radix-engine-interface = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "397a1bc0bb67429f1c82fbe0a62b3ac2f89a6fad" } @@ -109,7 +103,7 @@ itertools = { git = "https://github.com/rust-itertools/itertools/", rev = "98eca enum-as-inner = { git = "https://github.com/bluejekyll/enum-as-inner/", rev = "c15f6e5c4f98ec865e181ae1fff9fc13a1a2e4e2" } # uniffi = "0.27.1" -uniffi = { git = "https://github.com/mozilla/uniffi-rs/", rev = "6f33088e8100a2ea9586c8c3ecf98ab51d5aba62", features = [ +uniffi = { git = "https://github.com/sajjon/uniffi-rs/", rev = "d2886a5c836307e6b3f6f1a78e4b18b84eab3893", features = [ "cli", ] } @@ -141,18 +135,6 @@ url = { version = "2.5.0", features = ["serde"] } # paste = "1.0.14" paste = { git = "https://github.com/dtolnay/paste", rev = "1e0cc1025af5388397c67fa4389ad7ad24814df8" } -# regex = "1.9.3" -regex = { git = "https://github.com/rust-lang/regex/", rev = "72f889ef3cca59ebac6a026f3646e8d92f056d88", optional = true } - -# clap = "4.5.1" -clap = { git = "https://github.com/clap-rs/clap/", rev = "8a7a13a5618cfdc4ff328624a5266e7b4d88649a", default-features = false, features = [ - "std", - "derive", -], optional = true } - -# camino = "1.0.8" -camino = { git = "https://github.com/camino-rs/camino/", rev = "afa51b1b4c684b7e6698a6717ccda3affd0abd42", optional = true } - # async-trait = "0.1.79" async-trait = { git = "https://github.com/dtolnay/async-trait", rev = "1eb21ed8bd87029bf4dcbea41ff309f2b2220c43" } @@ -171,12 +153,6 @@ aes-gcm = { git = "https://github.com/RustCrypto/AEADs", rev = "7e82b01cd4901f6a # hkdf = "0.12.4" hkdf = { git = "https://github.com/RustCrypto/KDFs/", rev = "1ac16e8b9d4ee7a67613c9396c6cc1327652eaba" } -[dev-dependencies] -# uniffi = "0.27.1" -uniffi = { git = "https://github.com/mozilla/uniffi-rs/", rev = "6f33088e8100a2ea9586c8c3ecf98ab51d5aba62", features = [ - "bindgen-tests", -] } - # reqwest = "0.12.3" reqwest = { git = "https://github.com/seanmonstar/reqwest", rev = "0720159f6369f54e045a1fd315e0f24b7a0b4a39", default-features = false, features = [ "native-tls-vendored", @@ -184,15 +160,3 @@ reqwest = { git = "https://github.com/seanmonstar/reqwest", rev = "0720159f6369f # actix-rt = "3.3.0" actix-rt = { git = "https://github.com/actix/actix-net", rev = "57fd6ea8098d1f2d281c305fc331216c4fe1992e" } - -[build-dependencies] -# uniffi = "0.27.1" -uniffi = { git = "https://github.com/mozilla/uniffi-rs/", rev = "6f33088e8100a2ea9586c8c3ecf98ab51d5aba62", features = [ - "build", -] } - -# cargo_toml = "0.15.3" -cargo_toml = { git = "https://gitlab.com/lib.rs/cargo_toml", rev = "e498c94fc42a660c1ca1a28999ce1d757cfe77fe" } - -[features] -build-binary = ["camino", "clap", "regex"] diff --git a/Package.resolved b/Package.resolved index e8e0b7b8e..c3493322e 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,23 @@ { "pins" : [ + { + "identity" : "asyncextensions", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sideeffect-io/AsyncExtensions", + "state" : { + "revision" : "1f0729e4f1f6c7166acfac3cec43b3cbe83be0e6", + "version" : "0.5.2" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", + "version" : "1.1.0" + } + }, { "identity" : "swift-custom-dump", "kind" : "remoteSourceControl", @@ -23,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "b13b1d1a8e787a5ffc71ac19dcaf52183ab27ba2", - "version" : "1.1.1" + "revision" : "6f30bdba373bbd7fbfe241dddd732651f2fbd1e2", + "version" : "1.1.2" } } ], diff --git a/Package.swift b/Package.swift index 206b44939..2fc7dd18d 100644 --- a/Package.swift +++ b/Package.swift @@ -7,8 +7,6 @@ var swiftSettings: [SwiftSetting] = [ .enableExperimentalFeature("StrictConcurrency") ] -var strictSwiftSettings: [SwiftSetting] = swiftSettings - let sargonBinaryTargetName = "SargonCoreRS" let binaryTarget: Target let useLocalFramework = true @@ -20,12 +18,6 @@ if useLocalFramework { // import SargonCore unless you specify this as a relative path! path: "./target/swift/libsargon-rs.xcframework" ) - - // MUST NOT be part of release, since results in compilation error: - // The package product 'Sargon' cannot be used as a dependency of this target because it uses unsafe build flags. - strictSwiftSettings.append( - .unsafeFlags(["-warnings-as-errors"]) - ) } else { let releaseTag = "0.1.0" let releaseChecksum = "befef7d56108305ff6ff69d67483471395c3e603e299b3b15f5a826328de272b" @@ -49,8 +41,21 @@ let package = Package( ) ], dependencies: [ + // We use XCTestDynamicOverlay to have different `description` of e.g. Decimal192 + // for tests vs not tests (we use a .test `Locale`) + .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.1.2"), + + // `XCTAssertNoDifference` used in test .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.0"), + + // Hopefully only temporary! We use `SwiftJSON` to be able to mark some Sargon models + // as `Swift.Codable`. See the SargonObjectCodable protocol for details. + // In the future hopefully no JSON coding happens in wallets, + // i.e. Sargon does ALL JSON coding, then we can remove this. .package(url: "https://github.com/SwiftyJSON/SwiftyJSON", from: "5.0.2"), + + // Multicast / Share of notifications in EventBus + .package(url: "https://github.com/sideeffect-io/AsyncExtensions", exact: "0.5.2"), ], targets: [ binaryTarget, @@ -64,6 +69,8 @@ let package = Package( dependencies: [ .target(name: "SargonUniFFI"), "SwiftyJSON", + .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), + "AsyncExtensions" ], path: "apple/Sources/Sargon", swiftSettings: swiftSettings @@ -75,7 +82,7 @@ let package = Package( .product(name: "CustomDump", package: "swift-custom-dump"), ], path: "apple/Tests", - swiftSettings: strictSwiftSettings + swiftSettings: swiftSettings ), ] ) diff --git a/README.md b/README.md index 1fcd5c9b2..68cdf19f9 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,10 @@ This repo contains a [`.pre-commit-config.yaml`](./.pre-commit-config.yaml) whic pre-commit install ``` +## `direnv` + +Install [`direnv`](https://direnv.net) to automatically load env variables when you change directory to Sargon dir. This repo contains an `.envrc` which exports `RUST_LOG=info` so that logs are shown when running unit tests. When you run `cargo test` those logs will show up. Prefer using `nextest` below if you dont wanna see logs, and want prettier test result output. + ### `nextest` [Nextest](https://nexte.st/index.html) is a nice test runner for Rust! diff --git a/apple/Sources/Sargon/Antennas/Network/NetworkAntenna+URLSession.swift b/apple/Sources/Sargon/Antennas/Network/NetworkAntenna+URLSession.swift deleted file mode 100644 index 541ed8f10..000000000 --- a/apple/Sources/Sargon/Antennas/Network/NetworkAntenna+URLSession.swift +++ /dev/null @@ -1,48 +0,0 @@ -import Foundation -import SargonUniFFI - -extension URLRequest { - init(sargon: NetworkRequest) { - var request = URLRequest(url: sargon.url) - switch sargon.method { - case .post: - request.httpMethod = "POST" // FIXME: embed in sargon - case .get: - request.httpMethod = "GET" - } - - request.httpBody = sargon.body - request.allHTTPHeaderFields = sargon.headers - self = request - } -} - -extension NetworkResponse { - init(response: (Data, URLResponse)) throws { - guard let httpURLResponse = response.1 as? HTTPURLResponse else { - throw SargonError.NetworkRequestGenericFailure( - underlying: "Failed to cast to HTTPURLResponse") - } - self.init( - statusCode: UInt16(httpURLResponse.statusCode), - body: response.0 - ) - } -} - -extension URLSession: NetworkAntenna { - - public func executeNetworkRequest( - request sargonRequest: NetworkRequest - ) async throws -> NetworkResponse { - let request = URLRequest(sargon: sargonRequest) - let response: (Data, URLResponse) - do { - response = try await data(for: request) - } catch { - throw SargonError.NetworkRequestGenericFailure( - underlying: String(describing: error)) - } - return try NetworkResponse(response: response) - } -} diff --git a/apple/Sources/Sargon/Drivers/EntropyProvider/EntropyProvider+RandomData.swift b/apple/Sources/Sargon/Drivers/EntropyProvider/EntropyProvider+RandomData.swift new file mode 100644 index 000000000..5b109846f --- /dev/null +++ b/apple/Sources/Sargon/Drivers/EntropyProvider/EntropyProvider+RandomData.swift @@ -0,0 +1,34 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-03. +// + +import Foundation +import SargonUniFFI + +// Makes it possible to type `.shared` on an initalizer/func taking +// `some EntropyProviderDriver` as parameter. +extension EntropyProviderDriver where Self == EntropyProvider { + /// Singleton `EntropyProviderDriver` of type `EntropyProvider`, + /// being an `actor` that uses CSRNG `SystemRandomNumberGenerator` + public static var shared: Self { Self.shared } +} + +/// An `EntropyProviderDriver` actor which uses CSRNG `SystemRandomNumberGenerator` +/// to generate 32 bytes. +public final actor EntropyProvider { + internal init() {} + + /// Singleton `EntropyProviderDriver` of type `EntropyProvider`, + /// being an `actor` that uses CSRNG `SystemRandomNumberGenerator` + public static let shared = EntropyProvider() +} + +extension EntropyProvider: EntropyProviderDriver { + /// Generates 32 bytes using CSRNG `SystemRandomNumberGenerator` + nonisolated public func generateSecureRandomBytes() -> Entropy32Bytes { + Entropy32Bytes.generate() + } +} diff --git a/apple/Sources/Sargon/Drivers/EventBus/EventBus.swift b/apple/Sources/Sargon/Drivers/EventBus/EventBus.swift new file mode 100644 index 000000000..fc6ee9e3d --- /dev/null +++ b/apple/Sources/Sargon/Drivers/EventBus/EventBus.swift @@ -0,0 +1,61 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import SargonUniFFI +import AsyncExtensions + +// Makes it possible to type `.shared` on an initalizer/func taking +// `some EventBusDriver` as parameter. +extension EventBusDriver where Self == EventBus { + + /// Singleton `EventBusDriver` of type `EventBus` being an `actor` which forwards `EventNotification`s + /// originally emitted by `SargonOS` (Rust side). + public static var shared: Self { Self.shared } +} + +/// An `EventBusDriver` actor which handles incoming +/// `EventNotifications` and forwards them to any +/// subscriber of `notifications()`, being a multicasted +/// async sequence. +public final actor EventBus { + /// A stream we multicast on. + private let stream = AsyncThrowingPassthroughSubject() + private let subject: Subject + public init() { + subject = .init() + } +} + +extension EventBus { + + public typealias Element = EventNotification + public typealias Subject = AsyncPassthroughSubject + + /// Singleton `EventBusDriver` of type `EventBus` being an `actor` which forwards `EventNotification`s + /// originally emitted by `SargonOS` (Rust side). + public static let shared = EventBus() + + /// A multicasted async sequence of `EventNotification` values + /// over time, originally emitted by `SargonOS` (Rust side). + public func notifications() -> AsyncMulticastSequence> { + subject + .multicast(stream) + .autoconnect() + } +} + +extension EventBus: EventBusDriver { + /// This method is called by `SargonOS` (Rust side) and we should + /// "forward" the events to subscribers (Swift swide), i.e. `@SharedReader`s of profile values, + /// which uses `notifications()` to subscribe to these + /// values. + public func handleEventNotification(eventNotification: EventNotification) async { + log.debug("Handle event: \(String(describing: eventNotification.event))") + subject.send(eventNotification) + } +} diff --git a/apple/Sources/Sargon/Drivers/EventBus/EventNotification+Swiftified.swift b/apple/Sources/Sargon/Drivers/EventBus/EventNotification+Swiftified.swift new file mode 100644 index 000000000..ea5699acc --- /dev/null +++ b/apple/Sources/Sargon/Drivers/EventBus/EventNotification+Swiftified.swift @@ -0,0 +1,44 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-15. +// + +import Foundation +import SargonUniFFI + +extension EventProfileModified { + public var addedAccount: AccountAddress? { + guard case let .addedAccount(address) = self else { return nil } + return address + } +} + +extension Event { + public var profileModified: EventProfileModified? { + switch self { + case let .modifiedProfile(change): return change + default: return nil + } + } + public var addressOfNewAccount: AccountAddress? { + profileModified?.addedAccount + } +} + +extension EventNotification: Comparable { + /// `EventNotification` are made `Comparable` by + /// sorting on `timestamp`. + public static func < (lhs: Self, rhs: Self) -> Bool { + lhs.timestamp < rhs.timestamp + } +} + +extension Event { + + /// Discriminant of the `Event`. + public var kind: EventKind { + eventKind(event: self) + } +} diff --git a/apple/Sources/Sargon/Drivers/FileSystem/FileSystemDriver+Data+ContentsOf+URL.swift b/apple/Sources/Sargon/Drivers/FileSystem/FileSystemDriver+Data+ContentsOf+URL.swift new file mode 100644 index 000000000..e67d16814 --- /dev/null +++ b/apple/Sources/Sargon/Drivers/FileSystem/FileSystemDriver+Data+ContentsOf+URL.swift @@ -0,0 +1,75 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import SargonUniFFI + +extension FileManager: @unchecked Sendable {} + +// Makes it possible to type `.shared` on an initalizer/func taking +// `some FileSystemDriver` as parameter. +extension FileSystemDriver where Self == FileSystem { + + /// Singleton `FileSystemDriver` of type `FileSystem`being an `actor` which + /// uses a `FileManager` for File I/O CRUD operations. + public static var shared: Self { Self.shared } +} + +/// `FileSystemDriver` being an `actor` which +/// uses a `FileManager` for File I/O CRUD operations. +public final actor FileSystem { + private let fileManager: FileManager + public init(fileManager: FileManager = .default) { + self.fileManager = fileManager + } + + /// Singleton `FileSystemDriver` of type `FileSystem`being an `actor` which + /// uses a `FileManager` for File I/O CRUD operations. + public static let shared = FileSystem(fileManager: .default) +} + +extension URL { + init(file string: String) { + self.init(filePath: string, directoryHint: .notDirectory) + } +} + +extension FileSystem { + private func with( + path: String, + _ io: @Sendable (URL) throws -> T + ) throws -> T { + let url = URL(file: path) + guard url.startAccessingSecurityScopedResource() else { + // FIXME CYON change to specific error + throw CommonError.Unknown + } + defer { url.stopAccessingSecurityScopedResource() } + return try io(url) + } +} + +extension FileSystem: FileSystemDriver { + + public func loadFromFile(path: String) async throws -> BagOfBytes? { + try with(path: path) { + try Data(contentsOf: $0) + } + } + + public func saveToFile(path: String, data: BagOfBytes) async throws { + try with(path: path) { + try data.write(to: $0) + } + } + + public func deleteFile(path: String) async throws { + try with(path: path) { + try fileManager.removeItem(at: $0) + } + } +} diff --git a/apple/Sources/Sargon/Drivers/HostInfo/HostInfoDriver+DeviceInfo.swift b/apple/Sources/Sargon/Drivers/HostInfo/HostInfoDriver+DeviceInfo.swift new file mode 100644 index 000000000..f777ae164 --- /dev/null +++ b/apple/Sources/Sargon/Drivers/HostInfo/HostInfoDriver+DeviceInfo.swift @@ -0,0 +1,88 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-03. +// + +import Foundation +import SargonUniFFI + +/// An `HostInfoDriver` actor being able to read host info, i.e +/// details about the iPhone the app is running on. +public final actor HostInfo { + fileprivate var appVersion: String + public init(appVersion: String) { + self.appVersion = appVersion + } +} + +extension HostInfo { + nonisolated public func hostAppVersion() async -> String { + await self.appVersion + } + /// We cannot read a stable device if on iOS. We return `nil` so that Rust Sargon can generate + /// and save a device identifier for us. + public func hostDeviceId() async -> DeviceId? { + nil + } + + public func hostDeviceVendor() async -> String { + "Apple" + } +} + +#if canImport(UIKit) +import UIKit +extension HostInfo: HostInfoDriver { + + + nonisolated public func hostDeviceName() async -> String { + await UIDevice.current.name + } + + nonisolated public func hostDeviceSystemVersion() async -> String { + await UIDevice.current.systemVersion + } + + nonisolated public func hostDeviceModel() async -> String { + await UIDevice.current.model + } +} +#else + +extension HostInfo: HostInfoDriver { + + nonisolated public func hostDeviceSystemVersion() async -> String { + let info = ProcessInfo.processInfo.operatingSystemVersion + return "\(info.majorVersion).\(info.minorVersion).\(info.patchVersion)" + } + + nonisolated public func hostDeviceModel() async -> String { + + let service = IOServiceGetMatchingService( + kIOMainPortDefault, + IOServiceMatching("IOPlatformExpertDevice") + ) + + guard + let modelData = IORegistryEntryCreateCFProperty( + service, + "model" as CFString, + kCFAllocatorDefault, + 0 + ) + .takeUnretainedValue() as? Data, + let modelString = String(data: modelData, encoding: .utf8) + else { + return "Unknown Model" + } + + return modelString.trimmingCharacters(in: .controlCharacters.union(.whitespaces)) + } + + nonisolated public func hostDeviceName() async -> String { + "Unknown Name" + } +} +#endif // canImport(UIKit) diff --git a/apple/Sources/Sargon/Drivers/Logging/LoggingDriver+SwiftLog.swift b/apple/Sources/Sargon/Drivers/Logging/LoggingDriver+SwiftLog.swift new file mode 100644 index 000000000..d20080b58 --- /dev/null +++ b/apple/Sources/Sargon/Drivers/Logging/LoggingDriver+SwiftLog.swift @@ -0,0 +1,149 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-03. +// + +import Foundation +import SargonUniFFI +import os + +/// A public globally accessible `Logger` with `category` "Swift" which +/// Swift Sargon uses, and which iOS wallet can use too, it uses the +/// `Log.shared.swiftLogger`. +public var log: Logger { + Log.shared.swiftLogger +} + +// Makes it possible to type `.shared` on an initalizer/func taking +// `some LoggingDriver` as parameter. +extension LoggingDriver where Self == Log { + public static var shared: Self { Self.shared } +} + +/// A `LoggingDriver` actor capable of logging on behalf of +/// Rust Sargon core, that is, when we write e.g. `debug!("hey Swift from Rust");` +/// in Rust code, that message will in fact be logged by a `os.Logger` held by this `Log` +/// actor. +public final actor Log { + + /// The `Logger` to which Rust delegates logged messages. + nonisolated fileprivate let rustLogger: Logger + + ///The `Logger` Swift Sargon uses to log messages, accessed + ///using global variable `log` (aliasa for `Log.shared.swiftLogger`). + nonisolated internal let swiftLogger: Logger + + private init( + subsystem: String = "Sargon", + rustCategory: String = "Rust", + swiftCategory: String = "Swift" + ) { + self.rustLogger = Logger( + subsystem: subsystem, + category: rustCategory + ) + self.swiftLogger = Logger( + subsystem: subsystem, + category: swiftCategory + ) + } + + /// LoggingDriver singleton, a shared actor. + public static let shared = Log() + +} + +// MARK: `LoggingDriver` conformance. +extension Log: LoggingDriver { + nonisolated public func log( + level: LogLevel, + msg: String + ) { + rustLogger.log( + level: .init(sargonLogLevel: level), + "\(msg)" + ) + } +} + +/// Makes it possible for iOS Wallet to later change the log level in Rust land +/// (remember, the Rust logger **uses the Swift logger** +/// but might suppress logging invocation if its logging facade's log level is too low.) +public func setRustLogLevel(_ level: Sargon.LogFilter) { + rustLoggerSetLevel(level: level) +} + +/// Makes it possible for iOS Wallet to later change the log level in Rust land +/// (remember, the Rust logger **uses the Swift logger** +/// but might suppress logging invocation if its logging facade's log level is too low.) +public func getRustLogLevel() -> Sargon.LogFilter { + rustLoggerGetLevel() +} + +/// This logging diagnos will tell Rust to log messages at every log level, +/// then it will log at each level using the "Swift logger" (`log`) as well, +/// this is useful from DEBUG menus to ensure logging works properly. +/// +/// You can adjust the used Log Level in Rust by calling +/// `setRustLogLevel` and then call this method again. +public func logSystemDiagnosis() { + let levels = LogLevel.allCases + print("logSystemDiagnosis - RUST") + rustLoggerLogAtEveryLevel() + print("logSystemDiagnosis - Swift") + levels.forEach { level in + log.log(level: .init(sargonLogLevel: level), "Swift test: '\(String(describing: level))'") + } +} + +extension LogFilter: CaseIterable { + public static let allCases: [Self] = rustLoggerGetAllFilters() +} + +extension LogLevel: CaseIterable { + public static let allCases: [Self] = rustLoggerGetAllLevels() +} + +extension Logger: @unchecked Sendable {} + +extension OSLogType { + + /// Rust has 5 log levels, so does Swift. + /// + /// The mapping might look a bit strange since we do NOT map `error` -> `error`, + /// neither do we map `debug` -> `debug`, instead we map the most serious Rust + /// log level to the most serious Swift log level, and the least serious Rust to least + /// serious to Swift. + init(sargonLogLevel sargon: Sargon.LogLevel) { + switch sargon { + case .error: + // yes this is correct we dont map `error` -> `error`. + self = .fault + case .warn: + // Swift does not have warn, we use error, and we use Swifts fault for Rust error. + self = .error + case .info: self = .info + case .debug: + // yes this is correct we dont map `debug` -> `debug`. + self = .default + case .trace: + // debug is Swifts least serious, and `trace` is Rust least serious. + self = .debug + } + } +} + + +extension OSLogType { + + init(sargonFilterLevel sargon: Sargon.LogFilter) { + if let level = LogLevel(rawValue: sargon.rawValue) { + self.init(sargonLogLevel: level) + } else { + self = .fault + } + } +} + diff --git a/apple/Sources/Sargon/Drivers/Networking/NetworkingDriver+URLSession.swift b/apple/Sources/Sargon/Drivers/Networking/NetworkingDriver+URLSession.swift new file mode 100644 index 000000000..0c2154351 --- /dev/null +++ b/apple/Sources/Sargon/Drivers/Networking/NetworkingDriver+URLSession.swift @@ -0,0 +1,30 @@ +import Foundation +import SargonUniFFI + + +// Makes it possible to type `.shared` on an initalizer/func taking +// `some NetworkingDriver` as parameter. +extension NetworkingDriver where Self == URLSession { + + /// Singleton `NetworkingDriver` of type `URLSession`, which + /// uses `URLSession.shared`. + public static var shared: Self { Self.shared } +} + +// MARK: `NetworkingDriver` conformance +extension URLSession: NetworkingDriver { + + public func executeNetworkRequest( + request sargonRequest: NetworkRequest + ) async throws -> NetworkResponse { + let request = URLRequest(sargon: sargonRequest) + let response: (Data, URLResponse) + do { + response = try await data(for: request) + } catch { + throw SargonError.NetworkRequestGenericFailure( + underlying: String(describing: error)) + } + return try NetworkResponse(response: response) + } +} diff --git a/apple/Sources/Sargon/Drivers/TestDrivers/SecureStorage/SecureStorageDriver+InsecureTestOnlyEphemeral_SecureStorage.swift b/apple/Sources/Sargon/Drivers/TestDrivers/SecureStorage/SecureStorageDriver+InsecureTestOnlyEphemeral_SecureStorage.swift new file mode 100644 index 000000000..5aa3bfb97 --- /dev/null +++ b/apple/Sources/Sargon/Drivers/TestDrivers/SecureStorage/SecureStorageDriver+InsecureTestOnlyEphemeral_SecureStorage.swift @@ -0,0 +1,42 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-03. +// + +import Foundation +import SargonUniFFI + +#if DEBUG +extension SecureStorageDriver where Self == Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage { + public init(keychainService: String) { + self.init(keychainService: keychainService) + } +} + +/// ‼️ NEVER USE IN PRODUCTION ‼️ +/// An INSECURE ephemeral storage conforming to `SecureStorageDriver` meant +/// for testing purposes only. +public final actor Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage { + public typealias Key = SecureStorageKey + fileprivate var dictionary: [Key: Data] = [:] + public init(keychainService _: String) {} +} + +// MARK: `SecureStorageDriver` conformance +extension Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage: SecureStorageDriver { + public func loadData(key: SecureStorageKey) async throws -> Data? { + dictionary[key] + } + + public func saveData(key: SecureStorageKey, data: Data) async throws { + dictionary[key] = data + } + + public func deleteDataForKey(key: SecureStorageKey) async throws { + dictionary.removeValue(forKey: key) + } + +} +#endif diff --git a/apple/Sources/Sargon/Drivers/TestDrivers/TestDrivers.swift b/apple/Sources/Sargon/Drivers/TestDrivers/TestDrivers.swift new file mode 100644 index 000000000..f4ebb3c61 --- /dev/null +++ b/apple/Sources/Sargon/Drivers/TestDrivers/TestDrivers.swift @@ -0,0 +1,30 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-13. +// + +import Foundation +import SargonUniFFI + +#if DEBUG + + +extension BIOS { + + public static func insecure( + bundle: Bundle = .main, + userDefaultsSuite: String = "test" + ) -> BIOS { + BIOS( + bundle: bundle, + userDefaultsSuite: userDefaultsSuite, + secureStorageDriver: Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage.init( + keychainService: "test" + ) + ) + } +} + +#endif diff --git a/apple/Sources/Sargon/Drivers/UnsafeStorage/UnsafeStorageDriver+UserDefaults.swift b/apple/Sources/Sargon/Drivers/UnsafeStorage/UnsafeStorageDriver+UserDefaults.swift new file mode 100644 index 000000000..546276c32 --- /dev/null +++ b/apple/Sources/Sargon/Drivers/UnsafeStorage/UnsafeStorageDriver+UserDefaults.swift @@ -0,0 +1,57 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import SargonUniFFI + +extension UserDefaults: @unchecked Sendable {} + +// Makes it possible to type `.shared` on an initalizer/func taking +// `some UnsafeStorageDriver` as parameter. +extension UnsafeStorageDriver where Self == UnsafeStorage { + + /// Singleton `UnsafeStorageDriver` of type `UnsafeStorage, + /// which uses `UserDefaults.standard` as storage + public static var shared: Self { Self.shared } +} + +/// An `UnsafeStorageDriver` implementation which +/// wraps `UserDefaults`. +public final class UnsafeStorage: Sendable { + public typealias Key = UnsafeStorageKey + fileprivate let userDefaults: UserDefaults + public init(userDefaults: UserDefaults = .standard) { + self.userDefaults = userDefaults + } + /// Singleton `UnsafeStorageDriver` of type `UnsafeStorage, + /// which uses `UserDefaults.standard` as storage + public static let shared = UnsafeStorage() +} + +extension UnsafeStorageKey { + /// Translates this `UnsafeStorageKey` into a String + /// identifier which we can use with `UserDefaults` + var identifier: String { + unsafeStorageKeyIdentifier(key: self) + } +} + +// MARK: `UnsafeStorageDriver` confirmance +extension UnsafeStorage: UnsafeStorageDriver { + public func loadData(key: Key) -> Data? { + userDefaults.data(forKey: key.identifier) + } + + public func saveData(key: Key, data: Data) { + userDefaults.setValue(data, forKey: key.identifier) + } + + public func deleteDataForKey(key: Key) { + userDefaults.removeObject(forKey: key.identifier) + } + +} diff --git a/apple/Sources/Sargon/Extensions/Methods/Crypto/Derivation/Mnemonic+Wrap+Functions.swift b/apple/Sources/Sargon/Extensions/Methods/Crypto/Derivation/Mnemonic+Wrap+Functions.swift index 78d5d143b..e83a11ca5 100644 --- a/apple/Sources/Sargon/Extensions/Methods/Crypto/Derivation/Mnemonic+Wrap+Functions.swift +++ b/apple/Sources/Sargon/Extensions/Methods/Crypto/Derivation/Mnemonic+Wrap+Functions.swift @@ -5,7 +5,7 @@ extension Mnemonic { public var phrase: String { mnemonicPhrase(from: self) } - + public init(wordCount: BIP39WordCount, language: BIP39Language) { let entropy: BIP39Entropy = switch wordCount { case .twentyFour: @@ -19,10 +19,10 @@ extension Mnemonic { case .twelve: BIP39Entropy.entropyOf16Bytes(.generate()) } - + self = newMnemonicGenerateWithEntropy(entropy: entropy, language: language) } - + public init(phrase: String, language: BIP39Language? = nil) throws { if let language { self = try newMnemonicFromPhraseLanguage(phrase: phrase, language: language) @@ -30,7 +30,7 @@ extension Mnemonic { self = try newMnemonicFromPhrase(phrase: phrase) } } - + public init(words: some Collection) throws { self = try newMnemonicFromWords(words: Array(words)) } diff --git a/apple/Sources/Sargon/Extensions/Methods/Crypto/Keys/PrivateHD+Wrap+Functions.swift b/apple/Sources/Sargon/Extensions/Methods/Crypto/Keys/PrivateHD+Wrap+Functions.swift index a5a424582..2505b603c 100644 --- a/apple/Sources/Sargon/Extensions/Methods/Crypto/Keys/PrivateHD+Wrap+Functions.swift +++ b/apple/Sources/Sargon/Extensions/Methods/Crypto/Keys/PrivateHD+Wrap+Functions.swift @@ -11,22 +11,24 @@ import SargonUniFFI extension PrivateHierarchicalDeterministicFactorSource { public static func olympia( - mnemonicWithPassphrase: MnemonicWithPassphrase + mnemonicWithPassphrase: MnemonicWithPassphrase, + deviceInfo: DeviceInfo ) -> Self { newPrivateHdFactorSourceOlympiaFromMnemonicWithPassphrase( mnemonicWithPassphrase: mnemonicWithPassphrase, - walletClientModel: .iphone + deviceInfo: deviceInfo ) } public static func babylon( isMainBDFS: Bool, - mnemonicWithPassphrase: MnemonicWithPassphrase + mnemonicWithPassphrase: MnemonicWithPassphrase, + deviceInfo: DeviceInfo ) -> Self { newPrivateHdFactorSourceBabylonFromMnemonicWithPassphrase( isMain: isMainBDFS, mnemonicWithPassphrase: mnemonicWithPassphrase, - walletClientModel: .iphone + deviceInfo: deviceInfo ) } } diff --git a/apple/Sources/Sargon/Extensions/Methods/Profile+Supporting+Types/AccountForDisplay+Wrap+Functions.swift b/apple/Sources/Sargon/Extensions/Methods/Profile+Supporting+Types/AccountForDisplay+Wrap+Functions.swift new file mode 100644 index 000000000..ef0c061b0 --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Methods/Profile+Supporting+Types/AccountForDisplay+Wrap+Functions.swift @@ -0,0 +1,16 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-16. +// + +import Foundation +import SargonUniFFI + +extension AccountForDisplay { + + public init(_ account: Account) { + self = newAccountForDisplayFromAccount(account: account) + } +} diff --git a/apple/Sources/Sargon/Extensions/Methods/Profile/DeviceInfo+Wrap+Functions.swift b/apple/Sources/Sargon/Extensions/Methods/Profile/DeviceInfo+Wrap+Functions.swift index 42ddfb163..2e3e56b3b 100644 --- a/apple/Sources/Sargon/Extensions/Methods/Profile/DeviceInfo+Wrap+Functions.swift +++ b/apple/Sources/Sargon/Extensions/Methods/Profile/DeviceInfo+Wrap+Functions.swift @@ -9,9 +9,6 @@ import Foundation import SargonUniFFI extension DeviceInfo { - public static func iPhone() -> Self { - newDeviceInfoIphone() - } public init(jsonData: some DataProtocol) throws { self = try newDeviceInfoFromJsonBytes(jsonBytes: Data(jsonData)) diff --git a/apple/Sources/Sargon/Extensions/Methods/Profile/Factor/DeviceFactorSource+Wrap+Functions.swift b/apple/Sources/Sargon/Extensions/Methods/Profile/Factor/DeviceFactorSource+Wrap+Functions.swift index ab6f73997..c0f46593e 100644 --- a/apple/Sources/Sargon/Extensions/Methods/Profile/Factor/DeviceFactorSource+Wrap+Functions.swift +++ b/apple/Sources/Sargon/Extensions/Methods/Profile/Factor/DeviceFactorSource+Wrap+Functions.swift @@ -11,22 +11,24 @@ import SargonUniFFI extension DeviceFactorSource { public static func olympia( - mnemonicWithPassphrase: MnemonicWithPassphrase + mnemonicWithPassphrase: MnemonicWithPassphrase, + deviceInfo: DeviceInfo ) -> Self { newDeviceFactorSourceOlympia( mnemonicWithPassphrase: mnemonicWithPassphrase, - walletClientModel: .iphone + deviceInfo: deviceInfo ) } public static func babylon( mnemonicWithPassphrase: MnemonicWithPassphrase, - isMain: Bool + isMain: Bool, + deviceInfo: DeviceInfo ) -> Self { newDeviceFactorSourceBabylon( isMain: isMain, mnemonicWithPassphrase: mnemonicWithPassphrase, - walletClientModel: .iphone + deviceInfo: deviceInfo ) } diff --git a/apple/Sources/Sargon/Extensions/Methods/System/Drivers/NetworkMethod+Wrap+Functions.swift b/apple/Sources/Sargon/Extensions/Methods/System/Drivers/NetworkMethod+Wrap+Functions.swift new file mode 100644 index 000000000..d05aa8a6e --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Methods/System/Drivers/NetworkMethod+Wrap+Functions.swift @@ -0,0 +1,8 @@ +import Foundation +import SargonUniFFI + +extension NetworkMethod { + public func toString() -> String { + networkMethodToString(method: self) + } +} diff --git a/apple/Sources/Sargon/Extensions/SampleValues/System/Drivers/NetworkMethod+SampleValues.swift b/apple/Sources/Sargon/Extensions/SampleValues/System/Drivers/NetworkMethod+SampleValues.swift new file mode 100644 index 000000000..ac60a02f2 --- /dev/null +++ b/apple/Sources/Sargon/Extensions/SampleValues/System/Drivers/NetworkMethod+SampleValues.swift @@ -0,0 +1,9 @@ +import Foundation +import SargonUniFFI + +#if DEBUG +extension NetworkMethod { + public static let sample: Self = newNetworkMethodSample() + public static let sampleOther: Self = newNetworkMethodSampleOther() +} +#endif // DEBUG diff --git a/apple/Sources/Sargon/Antennas/Network/GatewayClient+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/Clients/GatewayClient+Swiftified.swift similarity index 62% rename from apple/Sources/Sargon/Antennas/Network/GatewayClient+Swiftified.swift rename to apple/Sources/Sargon/Extensions/Swiftified/Clients/GatewayClient+Swiftified.swift index 0448aee73..f78f5f5cc 100644 --- a/apple/Sources/Sargon/Antennas/Network/GatewayClient+Swiftified.swift +++ b/apple/Sources/Sargon/Extensions/Swiftified/Clients/GatewayClient+Swiftified.swift @@ -3,6 +3,6 @@ import SargonUniFFI extension GatewayClient { public convenience init(networkID: NetworkID) { - self.init(networkAntenna: URLSession.shared, networkId: networkID) + self.init(networkingDriver: URLSession.shared, networkId: networkID) } } diff --git a/apple/Sources/Sargon/Extensions/Swiftified/Profile+Supporting+Types/AccountForDisplay+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/Profile+Supporting+Types/AccountForDisplay+Swiftified.swift index fea5817d3..e26dd3693 100644 --- a/apple/Sources/Sargon/Extensions/Swiftified/Profile+Supporting+Types/AccountForDisplay+Swiftified.swift +++ b/apple/Sources/Sargon/Extensions/Swiftified/Profile+Supporting+Types/AccountForDisplay+Swiftified.swift @@ -15,4 +15,5 @@ extension AccountForDisplay: Identifiable { public var id: ID { address } + } diff --git a/apple/Sources/Sargon/Extensions/Swiftified/System/BIOS/BIOS+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/System/BIOS/BIOS+Swiftified.swift new file mode 100644 index 000000000..231cbfcce --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Swiftified/System/BIOS/BIOS+Swiftified.swift @@ -0,0 +1,60 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-03. +// + +import Foundation +import SargonUniFFI + +public typealias BIOS = Bios +extension BIOS: @unchecked Sendable {} + + +extension Drivers { + public convenience init( + bundle: Bundle, + userDefaultsSuite: String, + secureStorageDriver: SecureStorageDriver + ) { + self.init( + appVersion: (bundle.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "Unknown", + userDefaultsSuite: userDefaultsSuite, + secureStorageDriver: secureStorageDriver + ) + } + + public convenience init( + appVersion: String, + userDefaultsSuite: String, + secureStorageDriver: SecureStorageDriver + ) { + self.init( + secureStorage: secureStorageDriver, + hostInfo: HostInfo(appVersion: appVersion), + unsafeStorage: UnsafeStorage( + userDefaults: .init(suiteName: userDefaultsSuite)! + ) + ) + } +} + +extension BIOS { + + public convenience init( + bundle: Bundle, + userDefaultsSuite: String, + secureStorageDriver: SecureStorageDriver + ) { + let drivers = Drivers( + bundle: bundle, + userDefaultsSuite: userDefaultsSuite, + secureStorageDriver: secureStorageDriver + ) + // https://en.wikipedia.org/wiki/Power-on_self-test + log.info("📬 BIOS POST (Power-On Self Test)") + + self.init(drivers: drivers) + } +} diff --git a/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Drivers+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Drivers+Swiftified.swift new file mode 100644 index 000000000..f87bd147a --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Drivers+Swiftified.swift @@ -0,0 +1,31 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-03. +// + +import Foundation +import SargonUniFFI + + +extension Drivers: @unchecked Sendable {} +extension Drivers { + + public convenience init( + secureStorage: SecureStorageDriver, + hostInfo: HostInfoDriver, + unsafeStorage: UnsafeStorage + ) { + self.init( + networking: .shared, + secureStorage: secureStorage, + entropyProvider: .shared, + hostInfo: hostInfo, + logging: .shared, + eventBus: .shared, + fileSystem: .shared, + unsafeStorage: unsafeStorage + ) + } +} diff --git a/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkMethod+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkMethod+Swiftified.swift new file mode 100644 index 000000000..9de356d34 --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkMethod+Swiftified.swift @@ -0,0 +1,9 @@ +import Foundation +import SargonUniFFI + +extension NetworkMethod: SargonModel {} +extension NetworkMethod: CustomStringConvertible { + public var description: String { + toString() + } +} diff --git a/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkRequest+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkRequest+Swiftified.swift new file mode 100644 index 000000000..d072c4d9f --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkRequest+Swiftified.swift @@ -0,0 +1,37 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-13. +// + +import Foundation +import SargonUniFFI + +extension URL { + public init(validating string: String) throws { + guard let url = Self.init(string: string) else { + throw SargonError.InvalidUrl(badValue: string) + } + self = url + } +} + +extension NetworkRequest { + public init( + validating urlString: String, + method: NetworkMethod, + headers: [String: String] = [:], + body: Data = .init() + ) throws { + let url = try URL( + validating: urlString + ) + self.init( + url: url, + method: method, + headers: headers, + body: body + ) + } +} diff --git a/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkRequest+URLRequest+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkRequest+URLRequest+Swiftified.swift new file mode 100644 index 000000000..8d7cfd4db --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkRequest+URLRequest+Swiftified.swift @@ -0,0 +1,21 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-03. +// + +import Foundation +import SargonUniFFI + +extension URLRequest { + init(sargon: NetworkRequest) { + var request = URLRequest(url: sargon.url) + request.httpMethod = sargon.method.toString() + request.httpBody = sargon.body + request.allHTTPHeaderFields = sargon.headers + self = request + } +} + + diff --git a/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkResponse+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkResponse+Swiftified.swift new file mode 100644 index 000000000..755426a66 --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Swiftified/System/Drivers/Networking/NetworkResponse+Swiftified.swift @@ -0,0 +1,22 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-03. +// + +import Foundation +import SargonUniFFI + +extension NetworkResponse { + init(response: (Data, URLResponse)) throws { + guard let httpURLResponse = response.1 as? HTTPURLResponse else { + throw SargonError.NetworkRequestGenericFailure( + underlying: "Failed to cast to HTTPURLResponse") + } + self.init( + statusCode: UInt16(httpURLResponse.statusCode), + body: response.0 + ) + } +} diff --git a/apple/Sources/Sargon/Extensions/Swiftified/System/SargonOS+Swiftified.swift b/apple/Sources/Sargon/Extensions/Swiftified/System/SargonOS+Swiftified.swift new file mode 100644 index 000000000..c88e86ac0 --- /dev/null +++ b/apple/Sources/Sargon/Extensions/Swiftified/System/SargonOS+Swiftified.swift @@ -0,0 +1,22 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-03. +// + +import Foundation +import SargonUniFFI + + +public typealias SargonOS = SargonOs + +extension SargonOS: @unchecked Sendable {} + +extension SargonOS { + + @available(*, deprecated, message: "SHOULD migrate to use more specialized methods on SargonOS instead, e.g. `createAndSaveNewAccount` - SargonOS should be the SOLE object to perform the mutation and persisting.") + public func saveChangedProfile(_ profile: Profile) async throws { + try await deprecatedSaveFfiChangedProfile(profile: profile) + } +} diff --git a/apple/Sources/Sargon/SargonOS/BIOS+Static+Shared.swift b/apple/Sources/Sargon/SargonOS/BIOS+Static+Shared.swift new file mode 100644 index 000000000..519973875 --- /dev/null +++ b/apple/Sources/Sargon/SargonOS/BIOS+Static+Shared.swift @@ -0,0 +1,50 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import SargonUniFFI + +extension BIOS { + private nonisolated(unsafe) static var _shared: BIOS! + + public nonisolated(unsafe) static var shared: BIOS { + guard let shared = Self._shared else { + fatalError("BIOS not created, create it with `BIOS.createShared:bundle:keychainService:userDefaultsSuite`") + } + return shared + } + + /// Can be access later with `OS.shared` + @discardableResult + public static func createdShared( + bundle: Bundle, + userDefaultsSuite: String, + secureStorageDriver: SecureStorageDriver + ) -> BIOS { + Self.settingShared( + shared: BIOS( + bundle: bundle, + userDefaultsSuite: userDefaultsSuite, + secureStorageDriver: secureStorageDriver + ) + ) + } + + /// Can be access later with `OS.shared` + @discardableResult + public static func settingShared( + shared: BIOS, + isEmulatingFreshInstall: Bool = false + ) -> BIOS { + if !isEmulatingFreshInstall { + assert(_shared == nil, "Should not be created twice") + } + Self._shared = shared + setRustLogLevel(.debug) + return shared + } +} diff --git a/apple/Sources/Sargon/SargonOS/SargonOS+SargonOSProtocol.swift b/apple/Sources/Sargon/SargonOS/SargonOS+SargonOSProtocol.swift new file mode 100644 index 000000000..ac6b02981 --- /dev/null +++ b/apple/Sources/Sargon/SargonOS/SargonOS+SargonOSProtocol.swift @@ -0,0 +1,24 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation + +extension SargonOS: SargonOSProtocol { + public var os: SargonOS { self } +} + +// MARK: SargonOSProtocol Conformance +extension SargonOS { + + @discardableResult + public func createAccount( + named accountName: DisplayName + ) async throws -> Account { + try await createAndSaveNewAccount(networkId: currentNetworkID, name: accountName) + } + +} diff --git a/apple/Sources/Sargon/SargonOS/SargonOS+Static+Shared.swift b/apple/Sources/Sargon/SargonOS/SargonOS+Static+Shared.swift new file mode 100644 index 000000000..029cbf15a --- /dev/null +++ b/apple/Sources/Sargon/SargonOS/SargonOS+Static+Shared.swift @@ -0,0 +1,40 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import SargonUniFFI + +struct SargonOSAlreadyBooted: LocalizedError { + var errorDescription: String? { + "Radix Wallet core already initialized, should not have been initialized twice. This is a Radix developer error." + } +} + +extension SargonOS { + internal nonisolated(unsafe) static var _shared: SargonOS! + + public nonisolated(unsafe) static var shared: SargonOS { + guard let shared = Self._shared else { + fatalError("OS not created, create it with `SargonOS.createdSharedBootingWith:bios`") + } + return shared + } + + /// Can be access later with `OS.shared` + @discardableResult + public static func createdSharedBootingWith( + bios: BIOS, + isEmulatingFreshInstall: Bool = false + ) async throws -> SargonOS { + if !isEmulatingFreshInstall, _shared != nil { + throw SargonOSAlreadyBooted() + } + let shared = try await SargonOS.boot(bios: bios) + Self._shared = shared + return shared + } +} diff --git a/apple/Sources/Sargon/SargonOS/SargonOSProtocol.swift b/apple/Sources/Sargon/SargonOS/SargonOSProtocol.swift new file mode 100644 index 000000000..751745782 --- /dev/null +++ b/apple/Sources/Sargon/SargonOS/SargonOSProtocol.swift @@ -0,0 +1,59 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import SargonUniFFI + +/// A protocol enabling us to write `TestOS` +public protocol SargonOSProtocol { + var os: SargonOS { get } + + func createAccount( + named accountName: DisplayName + ) async throws -> Account +} + +// MARK: Forward calls to `os` +extension SargonOSProtocol { + + public func createAccount( + named accountName: DisplayName + ) async throws -> Account { + try await os.createAccount(named: accountName) + } +} + +// MARK: Extensions +extension SargonOSProtocol { + + @available(*, deprecated, message: "SHOULD migrate to use more specialized access methods on SargonOS instead, e.g. `accountsOnCurrentNetwork`.") + public var profile: Profile { + os.profile() + } + + public var currentNetworkID: NetworkID { + os.currentNetworkId() + } + + public var gateways: SavedGateways { + os.gateways() + } + + @available(*, deprecated, message: "Consider using faster `accountsForDisplayOnCurrentNetwork` and follow up with ") + public var accountsOnCurrentNetwork: [Account] { + os.accountsOnCurrentNetwork() + } + + public var accountsForDisplayOnCurrentNetwork: [AccountForDisplay] { + os.accountsForDisplayOnCurrentNetwork() + } + + public func accountByAddress(_ address: AccountAddress) throws -> Account { + try os.accountByAddress(address: address) + } + +} diff --git a/apple/Sources/Sargon/SargonOS/TestOS.swift b/apple/Sources/Sargon/SargonOS/TestOS.swift new file mode 100644 index 000000000..3205db798 --- /dev/null +++ b/apple/Sources/Sargon/SargonOS/TestOS.swift @@ -0,0 +1,82 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import SargonUniFFI + +#if DEBUG + +extension BIOS { + public static func test( + bundle: Bundle = .main, + userDefaultsSuite: String = "Test", + secureStorageDriver: SecureStorageDriver + ) -> BIOS { + BIOS( + bundle: bundle, + userDefaultsSuite: userDefaultsSuite, + secureStorageDriver: secureStorageDriver + ) + } +} + +public final class TestOS { + public let os: SargonOS + + public init(os: SargonOS) { + self.os = os + } + + public convenience init(bios: BIOS) async throws { + let os = try await SargonOS.boot(bios: bios) + self.init(os: os) + } + + +} +extension TestOS: SargonOSProtocol {} + +// MARK: Private +extension TestOS { + private func nextAccountName() -> DisplayName { + let index = accountsForDisplayOnCurrentNetwork.count + return DisplayName(value: "Unnamed \(index)") + } +} + +// MARK: Public +extension TestOS { + + @discardableResult + public func createAccount( + named name: String? = nil + ) async throws -> Self { + + let accountName = try name.map { + try DisplayName( + validating: $0 + ) + } ?? nextAccountName() + + let _ = try await os.createAccount( + named: accountName + ) + return self + } + + @discardableResult + public func batchCreateAccounts( + count: UInt16, + namePrefix: DisplayName + ) async throws -> Self { + let _ = try await os.batchCreateManyAccountsThenSaveOnce(count: count, networkId: currentNetworkID, namePrefix: namePrefix.value) + return self + } +} + + +#endif // DEBUG diff --git a/apple/Tests/IntegrationTests/DriversTests/DriversTests.swift b/apple/Tests/IntegrationTests/DriversTests/DriversTests.swift new file mode 100644 index 000000000..22da9576a --- /dev/null +++ b/apple/Tests/IntegrationTests/DriversTests/DriversTests.swift @@ -0,0 +1,34 @@ +import CustomDump +import Foundation +import Sargon +import SargonUniFFI +import XCTest + + +#if DEBUG +extension Drivers { + public static func test() -> Drivers { + Drivers( + appVersion: "0.0.1", + userDefaultsSuite: "works.rdx", + secureStorageDriver: Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage( + keychainService: "test" + ) + ) + } + +} +#endif + + +final class DriversTests: TestCase { + typealias SUT = Drivers + + func test_log_at_each_level() { + rustLoggerLogAtEveryLevel() + } + + func test_bios_insecure() async throws { + let _ = try await SargonOS.boot(bios: BIOS.insecure()) + } +} diff --git a/apple/Tests/IntegrationTests/DriversTests/EntropyProviderDriverTests.swift b/apple/Tests/IntegrationTests/DriversTests/EntropyProviderDriverTests.swift new file mode 100644 index 000000000..ad38b2586 --- /dev/null +++ b/apple/Tests/IntegrationTests/DriversTests/EntropyProviderDriverTests.swift @@ -0,0 +1,22 @@ +import CustomDump +import Foundation +@testable import Sargon +import SargonUniFFI +import XCTest + +class EntropyProviderDriverTests: DriverTest { + + func test() { + let sut = SUT() + let n = 10 + XCTAssertEqual( + Set((0.. { + + func test() async throws { + let sut = SUT() + + + let task = Task { + var notifications = Set() + for await notification in await sut.notifications().prefix(3) { + notifications.insert(notification) + } + return notifications + } + + let bios = BIOS(drivers: .withEventBus(sut)) + let os = try await TestOS(bios: bios) + try await os.createAccount() + let notifications = await task.value + XCTAssertEqual(Set(notifications.map(\.event.kind)), Set([.booted, .profileSaved, .addedAccount])) + } + +} + +extension HostInfoDriver where Self == HostInfo { + static var shared: Self { + HostInfo(appVersion: "0.0.0") + } +} + +#if DEBUG +extension SecureStorageDriver where Self == Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage { + static var shared: Self { + Self(keychainService: "test") + } +} + + + + +extension Drivers { + + static func withNetworking(_ networking: some NetworkingDriver) -> Drivers { + Drivers( + networking: networking, + secureStorage: .shared, + entropyProvider: .shared, + hostInfo: .shared, + logging: .shared, + eventBus: .shared, + fileSystem: .shared, + unsafeStorage: .shared + ) + } + + static func withSecureStorage(_ secureStorage: some SecureStorageDriver) -> Drivers { + Drivers( + networking: .shared, + secureStorage: secureStorage, + entropyProvider: .shared, + hostInfo: .shared, + logging: .shared, + eventBus: .shared, + fileSystem: .shared, + unsafeStorage: .shared + + ) + } + + static func withSecureStorage(_ entropyProvider: some EntropyProviderDriver) -> Drivers { + Drivers( + networking: .shared, + secureStorage: .shared, + entropyProvider: entropyProvider, + hostInfo: .shared, + logging: .shared, + eventBus: .shared, + fileSystem: .shared, + unsafeStorage: .shared + ) + } + + static func withHostInfo(_ hostInfo: some HostInfoDriver) -> Drivers { + Drivers( + networking: .shared, + secureStorage: .shared, + entropyProvider: .shared, + hostInfo: hostInfo, + logging: .shared, + eventBus: .shared, + fileSystem: .shared, + unsafeStorage: .shared + ) + } + + static func withLogging(_ logging: some LoggingDriver) -> Drivers { + Drivers( + networking: .shared, + secureStorage: .shared, + entropyProvider: .shared, + hostInfo: .shared, + logging: logging, + eventBus: .shared, + fileSystem: .shared, + unsafeStorage: .shared + ) + } + + static func withEventBus(_ eventBus: some EventBusDriver) -> Drivers { + Drivers( + networking: .shared, + secureStorage: .shared, + entropyProvider: .shared, + hostInfo: .shared, + logging: .shared, + eventBus: eventBus, + fileSystem: .shared, + unsafeStorage: .shared + ) + } + + static func withEventBus(_ fileSystem: some FileSystemDriver) -> Drivers { + Drivers( + networking: .shared, + secureStorage: .shared, + entropyProvider: .shared, + hostInfo: .shared, + logging: .shared, + eventBus: .shared, + fileSystem: fileSystem, + unsafeStorage: .shared + ) + } + + static func withEventBus(_ unsafeStorage: some UnsafeStorageDriver) -> Drivers { + Drivers( + networking: .shared, + secureStorage: .shared, + entropyProvider: .shared, + hostInfo: .shared, + logging: .shared, + eventBus: .shared, + fileSystem: .shared, + unsafeStorage: unsafeStorage + ) + } +} +#endif diff --git a/apple/Tests/IntegrationTests/DriversTests/FileSystemDriverTests.swift b/apple/Tests/IntegrationTests/DriversTests/FileSystemDriverTests.swift new file mode 100644 index 000000000..d416319bb --- /dev/null +++ b/apple/Tests/IntegrationTests/DriversTests/FileSystemDriverTests.swift @@ -0,0 +1,21 @@ +import CustomDump +import Foundation +import Sargon +import SargonUniFFI +import XCTest + +class FileSystemDriverTests: DriverTest { + + func test() async throws { + let nameOfFile = "delete_me_\(UUID().uuidString).txt" + let path = URL.temporaryDirectory.appending(path: nameOfFile, directoryHint: .notDirectory).path() + let data = Data("This file is completely safe to delete. It was generated by a unit test.".utf8) + + let sut = SUT() + try await sut.saveToFile(path: path, data: data) + let loaded = try await sut.loadFromFile(path: path) + XCTAssertEqual(loaded, data) + try await sut.deleteFile(path: path) + + } +} diff --git a/apple/Tests/IntegrationTests/DriversTests/HostInfoDriverTests.swift b/apple/Tests/IntegrationTests/DriversTests/HostInfoDriverTests.swift new file mode 100644 index 000000000..f4b0d09fc --- /dev/null +++ b/apple/Tests/IntegrationTests/DriversTests/HostInfoDriverTests.swift @@ -0,0 +1,45 @@ +import CustomDump +import Foundation +import Sargon +import SargonUniFFI +import XCTest + +private let appVersion = "0.0.0" +extension HostInfo { + init() { + self.init(appVersion: appVersion) + } +} + +class HostInfoDriverTests: DriverTest { + + func test_app_version() async throws { + let sut = SUT() + let info = await sut.hostAppVersion() + XCTAssertEqual(info, appVersion) + } + + func test_device_id_is_nil() async throws { + let sut = SUT() + let id = await sut.hostDeviceId() + XCTAssertNil(id) + } + + func test_device_name_not_empty() async throws { + let sut = SUT() + let info = await sut.hostDeviceName() + XCTAssertFalse(info.isEmpty) + } + + func test_device_system_not_empty() async throws { + let sut = SUT() + let info = await sut.hostDeviceSystemVersion() + XCTAssertFalse(info.isEmpty) + } + + func test_device_model_not_empty() async throws { + let sut = SUT() + let info = await sut.hostDeviceModel() + XCTAssertFalse(info.isEmpty) + } +} diff --git a/apple/Tests/IntegrationTests/DriversTests/InsecureStorageTests.swift b/apple/Tests/IntegrationTests/DriversTests/InsecureStorageTests.swift new file mode 100644 index 000000000..dc7bffc99 --- /dev/null +++ b/apple/Tests/IntegrationTests/DriversTests/InsecureStorageTests.swift @@ -0,0 +1,20 @@ +import CustomDump +import Foundation +import Sargon +import SargonUniFFI +import XCTest + +class InsecureStorageDriverTests: DriverTest { + + func test() async throws { + let sut = SUT.init(keychainService: "test") + let data = Data.sampleAced + let key = SUT.Key.activeProfileId + try await sut.saveData(key: key, data: data) + let loaded = try await sut.loadData(key: key) + XCTAssertEqual(loaded, data) + try await sut.deleteDataForKey(key: key) + let loadedAfterDelete = try await sut.loadData(key: key) + XCTAssertNil(loadedAfterDelete) + } +} diff --git a/apple/Tests/IntegrationTests/DriversTests/LoggingDriverTests.swift b/apple/Tests/IntegrationTests/DriversTests/LoggingDriverTests.swift new file mode 100644 index 000000000..828a5dabd --- /dev/null +++ b/apple/Tests/IntegrationTests/DriversTests/LoggingDriverTests.swift @@ -0,0 +1,49 @@ +import CustomDump +import Foundation +@testable import Sargon +import SargonUniFFI +import XCTest +import os + +class LoggingDriverTests: DriverTest { + + func test() { + let sut = SUT.shared + let levels = LogLevel.allCases + levels.forEach { level in + let msg = "Swift Unit test \(#file) \(#line)" + sut.log(level: level, msg: msg) + sut.swiftLogger.log(level: .init(sargonLogLevel: level), "\(msg)") + } + } + + func test_setRustLogLevel() { + LogFilter.allCases.forEach { level in + setRustLogLevel(level) + XCTAssertEqual(getRustLogLevel(), level) + } + } + + func test_os_log_type_from_loglevel() { + func doTest(_ from: Sargon.LogLevel, _ expected: OSLogType) { + XCTAssertEqual(OSLogType(sargonLogLevel: from), expected) + } + doTest(.error, .fault) + doTest(.warn, .error) + doTest(.info, .info) + doTest(.debug, .default) + doTest(.trace, .debug) + } + + func test_os_log_type_from_filter() { + func doTest(_ from: Sargon.LogFilter, _ expected: OSLogType) { + XCTAssertEqual(OSLogType(sargonFilterLevel: from), expected) + } + doTest(.off, .fault) // yes inaccurate, but not too important, can fix later. + doTest(.error, .fault) + doTest(.warn, .error) + doTest(.info, .info) + doTest(.debug, .default) + doTest(.trace, .debug) + } +} diff --git a/apple/Tests/IntegrationTests/DriversTests/NetworkingDriverTests.swift b/apple/Tests/IntegrationTests/DriversTests/NetworkingDriverTests.swift new file mode 100644 index 000000000..6caab6913 --- /dev/null +++ b/apple/Tests/IntegrationTests/DriversTests/NetworkingDriverTests.swift @@ -0,0 +1,23 @@ +import CustomDump +import Foundation +import Sargon +import SargonUniFFI +import XCTest + +class NetworkingDriverTests: DriverTest { + + func test() async throws { + let sut = SUT.shared as NetworkingDriver + let response = try await sut.executeNetworkRequest( + request: .init( + validating: "https://radixdlt.com", + method: .head + ) + ) + XCTAssertEqual(response.statusCode, 200) + } + + func test_bad_url() { + XCTAssertThrowsError(try URL(validating: "")) + } +} diff --git a/apple/Tests/IntegrationTests/DriversTests/UnsafeStorageDriverTests.swift b/apple/Tests/IntegrationTests/DriversTests/UnsafeStorageDriverTests.swift new file mode 100644 index 000000000..70857ecd5 --- /dev/null +++ b/apple/Tests/IntegrationTests/DriversTests/UnsafeStorageDriverTests.swift @@ -0,0 +1,19 @@ +import CustomDump +import Foundation +import Sargon +import SargonUniFFI +import XCTest + + +class UnsafeStorageDriverTests: DriverTest { + + func test_crud() async throws { + let sut = SUT() + let key = UnsafeStorage.Key.factorSourceUserHasWrittenDown + let data = Data([0x01]) + sut.saveData(key: key, data: data) + XCTAssertEqual(sut.loadData(key: key), data) + sut.deleteDataForKey(key: key) + XCTAssertNil(sut.loadData(key: key)) + } +} diff --git a/apple/Tests/IntegrationTests/GatewayClientTests.swift b/apple/Tests/IntegrationTests/GatewayClientTests.swift index f9cf9c18b..86f8536af 100644 --- a/apple/Tests/IntegrationTests/GatewayClientTests.swift +++ b/apple/Tests/IntegrationTests/GatewayClientTests.swift @@ -55,7 +55,7 @@ final class NetworkAntennaTests: TestCase { }() - let failGateway = GatewayClient(networkAntenna: failURLSession, networkId: .mainnet) + let failGateway = GatewayClient(networkingDriver: failURLSession, networkId: .mainnet) do { _ = try await failGateway.xrdBalanceOfAccountOrZero(address: AccountAddress.sample) XCTFail("Expected to fail") diff --git a/apple/Tests/IntegrationTests/SargonOS/BIOSTests.swift b/apple/Tests/IntegrationTests/SargonOS/BIOSTests.swift new file mode 100644 index 000000000..9ffaa9d4e --- /dev/null +++ b/apple/Tests/IntegrationTests/SargonOS/BIOSTests.swift @@ -0,0 +1,18 @@ +import CustomDump +import Foundation +import Sargon +import SargonUniFFI +import XCTest + +final class BIOSTests: OSTest { + typealias SUT = BIOS + + func test_set_shared() { + let sut = SUT.createdShared(bundle: .main, userDefaultsSuite: "test", secureStorageDriver: Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage(keychainService: "test")) + XCTAssertTrue(SUT.shared === sut) + let new = SUT.settingShared(shared: .test(secureStorageDriver: Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage.init(keychainService: "other")), isEmulatingFreshInstall: true) + + XCTAssertFalse(sut === new) + XCTAssertTrue(SUT.shared === new) + } +} diff --git a/apple/Tests/IntegrationTests/SargonOS/SargonOSTests.swift b/apple/Tests/IntegrationTests/SargonOS/SargonOSTests.swift new file mode 100644 index 000000000..e742a1fc5 --- /dev/null +++ b/apple/Tests/IntegrationTests/SargonOS/SargonOSTests.swift @@ -0,0 +1,62 @@ +import CustomDump +import Foundation +@testable import Sargon +import SargonUniFFI +import XCTest + +final class SargonOSTests: OSTest { + typealias SUT = SargonOS + + override func setUp() { + super.setUp() + SUT._shared = nil + } + + func test() async throws { + let _ = try await SUT.boot( + bios: .init( + drivers: .test() + ) + ) + } + + func test_set_shared() async throws { + let sut = try await SUT.createdSharedBootingWith( + bios: BIOS.test( + secureStorageDriver: Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage( + keychainService: "test" + ) + ) + ) + XCTAssertTrue(SUT.shared === sut) + } + + func test_boot_twice_throws() async throws { + let bios = BIOS.test( + secureStorageDriver: Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage( + keychainService: "test" + ) + ) + let _ = try await SUT.createdSharedBootingWith(bios: bios) + do { + let _ = try await SUT.createdSharedBootingWith(bios: bios, isEmulatingFreshInstall: false) + XCTFail("Should have thrown") + } catch let err as SargonOSAlreadyBooted { + XCTAssertEqual(err.errorDescription, "Radix Wallet core already initialized, should not have been initialized twice. This is a Radix developer error.") + } catch { XCTFail("Wrong error type, expected: \(SargonOSAlreadyBooted.self)") } + } + + func test_boot_twice_does_not_throws_when_emulating_fresh_install() async throws { + let bios = BIOS.test( + secureStorageDriver: Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage( + keychainService: "test" + ) + ) + let first = try await SUT.createdSharedBootingWith(bios: bios) + let second = try await SUT.createdSharedBootingWith(bios: bios, isEmulatingFreshInstall: true) + XCTAssertFalse(first === second) + XCTAssertTrue(SUT.shared === second) + } + +} + diff --git a/apple/Tests/IntegrationTests/SargonOS/TestOSTests.swift b/apple/Tests/IntegrationTests/SargonOS/TestOSTests.swift new file mode 100644 index 000000000..ecf8603c1 --- /dev/null +++ b/apple/Tests/IntegrationTests/SargonOS/TestOSTests.swift @@ -0,0 +1,120 @@ +import CustomDump +import Foundation +import Sargon +import SargonUniFFI +import XCTest + +extension TestOS { + + public convenience init() async throws { + try await self.init( + bios: BIOS( + drivers: Drivers( + networking: .shared, + secureStorage: Insecure︕!TestOnly︕!Ephemeral︕!SecureStorage( + keychainService: UUID().uuidString + ), + entropyProvider: .shared, + hostInfo: .shared, + logging: .shared, + eventBus: EventBus(), + fileSystem: .shared, + unsafeStorage: UnsafeStorage( + userDefaults: .init( + suiteName: UUID().uuidString + )! + ) + ) + ) + ) + } +} + +final class TestOSTests: OSTest { + + func test_create_single_account_many_times() async throws { + let bus = EventBus() + let drivers = Drivers.withEventBus(bus) + let sut = try await TestOS(bios: .init(drivers: drivers)) + let n = 3 + + let task = Task { + var values = Set() + for await eventNotification in await bus.notifications().filter({ $0.event.addressOfNewAccount != nil }).prefix(n) { + values.insert(eventNotification) + } + return Array(values).sorted().map(\.event) + } + + try await sut + .createAccount() + .createAccount(named: "Foo") + .createAccount() + + let events = await task.value + XCTAssertEqual(sut.accountsForDisplayOnCurrentNetwork.map(\.displayName), ["Unnamed 0", "Foo", "Unnamed 2"]) + XCTAssertEqual(sut.accountsForDisplayOnCurrentNetwork.map(\.address), events.compactMap(\.addressOfNewAccount)) + } + + func test_create_account_returned() async throws { + let sut = try await TestOS() + let displayName: DisplayName = "New" + let account = try await sut.createAccount(named: displayName) + XCTAssertEqual(account.displayName, displayName) + XCTAssertEqual(sut.accountsForDisplayOnCurrentNetwork, [AccountForDisplay(account)]) + } + + func test_create_account_returned_can_be_looked_up() async throws { + let sut = try await TestOS() + let displayName: DisplayName = "New" + let account = try await sut.createAccount(named: displayName) + let lookedUp = try sut.accountByAddress(account.address) + XCTAssertEqual(lookedUp, account) + } + + func test_lookup_throws_for_unknown_accounts() async throws { + let sut = try await TestOS() + XCTAssertThrowsError(try sut.accountByAddress(.sample)) + } + + func test_new_profile_has_preset_gateways() async throws { + let sut = try await TestOS() + let gateways = sut.gateways + XCTAssertEqual(gateways, .preset) + } + + func test_if_replace_profile_throws() async throws { + let sut = try await TestOS() + var profile = sut.profile + profile.header.id = ProfileID() // mutate profile + do { + try await sut.os.saveChangedProfile(profile) + XCTFail("We expected to throw") + } catch { + /* We expected to throw */ + } + } + + func test_we_can_mutate_profile_in_swift_and_save_then_profile_is_updated() async throws { + let sut = try await TestOS() + var profile = sut.profile + let creatingDevice = profile.header.creatingDevice + let newCreatingDevice = DeviceInfo.sampleOther + XCTAssertNotEqual(newCreatingDevice, creatingDevice) + profile.header.creatingDevice = newCreatingDevice // mutate profile + try await sut.os.saveChangedProfile(profile) + XCTAssertEqual(sut.profile.header.creatingDevice, newCreatingDevice) // assert change worked + } + + func test_batch_create_many_accounts() async throws { + let sut = try await TestOS() + let n: UInt16 = 4 + try await sut.batchCreateAccounts(count: n, namePrefix: "Unnamed") + XCTAssertEqual(sut.accountsOnCurrentNetwork.map(\.displayName.value), (0.. { + + func test_get_description() { + XCTAssertEqual(SUT.get.description, "GET") + } + + func test_post_description() { + XCTAssertEqual(SUT.post.description, "POST") + } +} diff --git a/apple/Tests/TestCases/Profile/DeviceInfoTests.swift b/apple/Tests/TestCases/Profile/DeviceInfoTests.swift index 4ddab2aaa..5332efc4d 100644 --- a/apple/Tests/TestCases/Profile/DeviceInfoTests.swift +++ b/apple/Tests/TestCases/Profile/DeviceInfoTests.swift @@ -6,10 +6,6 @@ import XCTest final class DeviceInfoTests: Test { - func test_new_iphone() { - XCTAssertNotEqual(SUT.sample, SUT.iPhone()) - } - func test_not_codable_but_lower_level_json_methods_json_data_roundtrip() throws{ let sut = SUT.sample let json = sut.jsonData() diff --git a/apple/Tests/TestCases/Profile/Factor/DeviceFactorSourceTests.swift b/apple/Tests/TestCases/Profile/Factor/DeviceFactorSourceTests.swift index fcd7f1c19..0cf62b074 100644 --- a/apple/Tests/TestCases/Profile/Factor/DeviceFactorSourceTests.swift +++ b/apple/Tests/TestCases/Profile/Factor/DeviceFactorSourceTests.swift @@ -27,23 +27,23 @@ final class DeviceFactorSourceTests: SpecificFactorSourceTest { func test_new_babylon() { - let sut = SUT.babylon(isMainBDFS: true, mnemonicWithPassphrase: .sample) + let sut = SUT.babylon(isMainBDFS: true, mnemonicWithPassphrase: .sample, deviceInfo: .sample) XCTAssertTrue(sut.supportsBabylon) } func test_new_olympia() { - let sut = SUT.olympia(mnemonicWithPassphrase: .sample) + let sut = SUT.olympia(mnemonicWithPassphrase: .sample, deviceInfo: .sample) XCTAssertTrue(sut.supportsOlympia) } func test_kind_is_device() { - XCTAssertEqual(SUT.olympia(mnemonicWithPassphrase: .sample).factorSourceKind, .device) - XCTAssertEqual(SUT.babylon(isMainBDFS: true, mnemonicWithPassphrase: .sample).factorSourceKind, .device) + XCTAssertEqual(SUT.olympia(mnemonicWithPassphrase: .sample, deviceInfo: .sample).factorSourceKind, .device) + XCTAssertEqual(SUT.babylon(isMainBDFS: true, mnemonicWithPassphrase: .sample, deviceInfo: .sample).factorSourceKind, .device) } func test_is_main_bdfs_true() { - let sut = SUT.babylon(isMainBDFS: true, mnemonicWithPassphrase: .sample) + let sut = SUT.babylon(isMainBDFS: true, mnemonicWithPassphrase: .sample, deviceInfo: .sample) XCTAssertTrue(sut.factorSource.isMainBDFS) } func test_is_main_bdfs_false() { - let sut = SUT.babylon(isMainBDFS: false, mnemonicWithPassphrase: .sample) + let sut = SUT.babylon(isMainBDFS: false, mnemonicWithPassphrase: .sample, deviceInfo: .sample) XCTAssertFalse(sut.factorSource.isMainBDFS) } } diff --git a/apple/Tests/TestCases/Profile/HeaderTests.swift b/apple/Tests/TestCases/Profile/HeaderTests.swift index 2f340d312..ae64e0558 100644 --- a/apple/Tests/TestCases/Profile/HeaderTests.swift +++ b/apple/Tests/TestCases/Profile/HeaderTests.swift @@ -64,12 +64,12 @@ final class HeaderTests: Test
{ "creatingDevice": { "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", "date": "2023-09-11T16:05:56.000Z", - "description": "iPhone" + "description": { "name": "iPhone", "model": "iPhone" } }, "lastUsedOnDevice": { "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", "date": "2023-09-11T16:05:56.000Z", - "description": "iPhone" + "description": { "name": "iPhone", "model": "iPhone" } }, "lastModified": "2023-09-11T16:05:56.000Z", "contentHint": { @@ -80,9 +80,9 @@ final class HeaderTests: Test
{ }, { "lastUsedOnDevice" : { - "date" : "2023-12-20T16:05:56.000Z", - "description" : "iPhone", - "id" : "aabbccdd-a9d9-49e5-8152-beefbeefbeef" + "date" : "2023-12-24T17:13:56.123Z", + "description" : { "name": "Android", "model": "Android" }, + "id" : "F07CA662-D9A9-9E45-1582-ACA773D174DD" }, "id" : "87654321-bbbb-cccc-dddd-87654321dcba", "contentHint" : { @@ -91,19 +91,19 @@ final class HeaderTests: Test
{ "numberOfPersonasOnAllNetworksInTotal" : 0 }, "creatingDevice" : { - "description" : "iPhone", - "id" : "aabbccdd-a9d9-49e5-8152-beefbeefbeef", - "date" : "2023-12-20T16:05:56.000Z" + "description" : { "name": "Android", "model": "Android" }, + "id" : "F07CA662-D9A9-9E45-1582-ACA773D174DD", + "date" : "2023-12-24T17:13:56.123Z" }, "snapshotVersion" : 100, - "lastModified" : "2023-12-20T16:05:56.000Z" + "lastModified" : "2023-12-24T17:13:56.123Z" } ] """.data(using: .utf8)! // test decoding let headerList = try JSONDecoder().decode([SUT].self, from: raw) - XCTAssertEqual(headerList, [SUT.sample, SUT.sampleOther]) + XCTAssertNoDifference(headerList, [SUT.sample, SUT.sampleOther]) // test encoding let encoded = try JSONEncoder().encode(headerList) diff --git a/apple/Tests/TestCases/Profile/ProfileTests.swift b/apple/Tests/TestCases/Profile/ProfileTests.swift index d3da917c6..344c0210f 100644 --- a/apple/Tests/TestCases/Profile/ProfileTests.swift +++ b/apple/Tests/TestCases/Profile/ProfileTests.swift @@ -98,8 +98,8 @@ final class ProfileTests: Test { let decoded = try SUT(jsonData: json) XCTAssertEqual(decoded, sut) } - var vectors = Array(zip(SUT.sampleValues, SUT.sampleValues.map { $0.jsonData() })) - vectors.append(vector) + let vectors = Array(zip(SUT.sampleValues, SUT.sampleValues.map { $0.jsonData() })) +// vectors.append(vector) try vectors.forEach(doTest) } diff --git a/apple/Tests/Utils/DriverTest.swift b/apple/Tests/Utils/DriverTest.swift new file mode 100644 index 000000000..3bedc876e --- /dev/null +++ b/apple/Tests/Utils/DriverTest.swift @@ -0,0 +1,9 @@ +import CustomDump +import Foundation +@testable import Sargon +import SargonUniFFI +import XCTest + +class DriverTest: TestCase { + typealias SUT = SUT_ +} diff --git a/apple/Tests/Utils/OSTest.swift b/apple/Tests/Utils/OSTest.swift new file mode 100644 index 000000000..04c9dba8a --- /dev/null +++ b/apple/Tests/Utils/OSTest.swift @@ -0,0 +1,15 @@ +import CustomDump +import Foundation +import Sargon +import SargonUniFFI +import XCTest + +class OSTest: TestCase { + + override class func shouldEnableRustLog() -> Bool { + // BIOS and SargonOS tests will have enabled Rust logging from inside of rust. + // we should not enable it twice which will lead to crash. + false + } + +} diff --git a/apple/Tests/Utils/Test.swift b/apple/Tests/Utils/Test.swift index b36ec0e50..1fb64deb4 100644 --- a/apple/Tests/Utils/Test.swift +++ b/apple/Tests/Utils/Test.swift @@ -5,6 +5,18 @@ import SargonUniFFI import XCTest class TestCase: XCTestCase { + + class func shouldEnableRustLog() -> Bool { + false + } + + class override func setUp() { + if shouldEnableRustLog() { + rustLoggerInit() + } + super.setUp() + } + override func setUp() { self.continueAfterFailure = false } diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 98d8318b6..000000000 --- a/codecov.yml +++ /dev/null @@ -1,22 +0,0 @@ -codecov: - notify: - wait_for_ci: true - max_report_age: off - require_ci_to_pass: true -comment: - behavior: default - layout: "reach, diff, flags, files" - show_carryforward_flags: false -coverage: - precision: 1 - range: 97...98 # red -> yellow (the inside range) -> green - status: - patch: - default: - target: auto - threshold: 80% - base: auto - only_pulls: true -flag_management: - default_rules: - carryforward: true diff --git a/crates/clients/Cargo.toml b/crates/clients/Cargo.toml new file mode 100644 index 000000000..65f0ffa9c --- /dev/null +++ b/crates/clients/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "clients" +version = "1.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } +serde = { workspace = true } +strum = { workspace = true } +log = { workspace = true } +reqwest = { workspace = true } +serde_json = { workspace = true } +pretty_env_logger = { workspace = true } +pretty_assertions = { workspace = true } +async-trait = { workspace = true } +enum-iterator = { workspace = true } +actix-rt = { workspace = true } +profile = { path = "../profile" } +sargoncommon = { path = "../common" } +drivers = { path = "../drivers" } +ret = { path = "../ret" } +gateway_models = { path = "../gateway_models" } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } + +[lib] +crate-type = ["staticlib", "cdylib", "lib"] diff --git a/crates/clients/build.rs b/crates/clients/build.rs new file mode 100644 index 000000000..ba02c3367 --- /dev/null +++ b/crates/clients/build.rs @@ -0,0 +1,4 @@ +pub fn main() { + uniffi::generate_scaffolding("src/clients.udl") + .expect("Should be able to build."); +} diff --git a/crates/clients/src/clients.udl b/crates/clients/src/clients.udl new file mode 100644 index 000000000..b18424f81 --- /dev/null +++ b/crates/clients/src/clients.udl @@ -0,0 +1 @@ +namespace clients {}; diff --git a/crates/clients/src/clients/README.md b/crates/clients/src/clients/README.md new file mode 100644 index 000000000..8f868b78f --- /dev/null +++ b/crates/clients/src/clients/README.md @@ -0,0 +1,3 @@ +# Clients + +A "client" needs a "driver", just like a "subsystem", however, unlike the subsystems the clients are typically owned, controlled and used by the SargonOS itself. diff --git a/crates/clients/src/clients/client/entropy_client/entropy_client.rs b/crates/clients/src/clients/client/entropy_client/entropy_client.rs new file mode 100644 index 000000000..b1b4ff92a --- /dev/null +++ b/crates/clients/src/clients/client/entropy_client/entropy_client.rs @@ -0,0 +1,18 @@ +use crate::prelude::*; + +#[derive(Debug)] +pub struct EntropyClient { + driver: Arc, +} + +impl EntropyClient { + pub fn new(driver: Arc) -> Self { + Self { driver } + } + + pub fn bip39_entropy(&self) -> NonEmptyMax32Bytes { + let entropy = self.driver.generate_secure_random_bytes(); + NonEmptyMax32Bytes::try_from(entropy.as_ref()) + .expect("Entropy is not empty") + } +} diff --git a/crates/clients/src/clients/client/entropy_client/mod.rs b/crates/clients/src/clients/client/entropy_client/mod.rs new file mode 100644 index 000000000..6fa5c43c3 --- /dev/null +++ b/crates/clients/src/clients/client/entropy_client/mod.rs @@ -0,0 +1,3 @@ +mod entropy_client; + +pub use entropy_client::*; diff --git a/crates/clients/src/clients/client/event_bus_client/event_bus_client.rs b/crates/clients/src/clients/client/event_bus_client/event_bus_client.rs new file mode 100644 index 000000000..58d08a2e9 --- /dev/null +++ b/crates/clients/src/clients/client/event_bus_client/event_bus_client.rs @@ -0,0 +1,20 @@ +use crate::prelude::*; + +#[derive(Debug)] +pub struct EventBusClient { + driver: Arc, +} + +impl EventBusClient { + pub fn new(driver: Arc) -> Self { + Self { driver } + } +} + +impl EventBusClient { + pub async fn emit(&self, event_notification: EventNotification) { + self.driver + .handle_event_notification(event_notification) + .await + } +} diff --git a/crates/clients/src/clients/client/event_bus_client/mod.rs b/crates/clients/src/clients/client/event_bus_client/mod.rs new file mode 100644 index 000000000..f5a8f1dba --- /dev/null +++ b/crates/clients/src/clients/client/event_bus_client/mod.rs @@ -0,0 +1,3 @@ +mod event_bus_client; + +pub use event_bus_client::*; diff --git a/crates/clients/src/clients/client/file_system_client/file_system_client.rs b/crates/clients/src/clients/client/file_system_client/file_system_client.rs new file mode 100644 index 000000000..4abc0cf07 --- /dev/null +++ b/crates/clients/src/clients/client/file_system_client/file_system_client.rs @@ -0,0 +1,162 @@ +use crate::prelude::*; +use std::path::Path; + +#[derive(Debug)] +pub struct FileSystemClient { + #[allow(dead_code)] + driver: Arc, +} + +impl FileSystemClient { + pub fn new(driver: Arc) -> Self { + Self { driver } + } +} + +#[allow(dead_code)] +pub fn path_to_string(path: impl AsRef) -> Result { + path.as_ref() + .to_str() + .ok_or(CommonError::Unknown) + .map(|s| s.to_owned()) +} + +#[allow(dead_code)] +impl FileSystemClient { + async fn load_from_file( + &self, + path: impl AsRef, + ) -> Result> { + let path = path_to_string(path.as_ref())?; + self.driver + .load_from_file(path) + // tarpaulin will incorrectly flag next line is missed + .await + } + + async fn save_to_file( + &self, + path: impl AsRef, + data: impl AsRef<[u8]>, + ) -> Result<()> { + let path = path_to_string(path.as_ref())?; + let data = BagOfBytes::from(data.as_ref()); + self.driver + .save_to_file(path, data) + // tarpaulin will incorrectly flag next line is missed + .await + } + + async fn delete_file(&self, path: impl AsRef) -> Result<()> { + let path = path_to_string(path.as_ref())?; + self.driver + .delete_file(path) + // tarpaulin will incorrectly flag next line is missed + .await + } +} + +#[cfg(test)] +mod tests { + + use std::path::PathBuf; + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = FileSystemClient; + + fn file_in_dir(dir_path: impl AsRef) -> PathBuf { + let dir_path = dir_path.as_ref(); + assert!(std::fs::create_dir_all(dir_path).is_ok()); + + let file_name = format!("delete-this--generated-by-test-{}.txt", id()); + dir_path.join(file_name) + } + + fn file_in_tmp() -> PathBuf { + let dir_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("target/tmp"); + file_in_dir(dir_path) + } + + fn make_contents(suffix: impl AsRef) -> BagOfBytes { + let suffix = suffix.as_ref(); + let str = format!("this file is completely safe to delete. it was generated by FileSystemClient test. Suffix: {}", suffix); + BagOfBytes::from(str.as_bytes()) + } + fn contents() -> BagOfBytes { + make_contents("first") + } + fn other_contents() -> BagOfBytes { + make_contents("second") + } + + impl FileSystemClient { + pub fn test() -> Self { + Self::new(RustFileSystemDriver::new()) + } + } + + #[actix_rt::test] + async fn test_create_load_delete() { + let sut = SUT::test(); + let file = file_in_tmp(); + + let data = contents(); + sut.save_to_file(file.clone(), data.clone()).await.unwrap(); + let loaded = sut.load_from_file(file.clone()).await.unwrap().unwrap(); + assert_eq!(loaded, data); + + // Assert can be updated + let new_data = other_contents(); + sut.save_to_file(file.clone(), new_data.clone()) + .await + .unwrap(); + let loaded = sut.load_from_file(file.clone()).await.unwrap().unwrap(); + assert_eq!(loaded, new_data); + + assert!(sut.delete_file(file.clone()).await.is_ok()); + } + + #[actix_rt::test] + async fn test_load_non_existing_is_ok() { + let sut = SUT::test(); + let res = sut.load_from_file("non-existing".to_owned()).await; + assert_eq!(res, Ok(None)); + } + + #[actix_rt::test] + async fn test_load_fail() { + let sut = SUT::test(); + let res = sut.load_from_file("/".to_owned()).await; + assert_eq!(res, Err(CommonError::Unknown)); + } + + #[actix_rt::test] + async fn test_delete_non_existing_is_ok() { + let sut = SUT::test(); + let res = sut.delete_file("does not exist".to_owned()).await; + assert_eq!(res, Ok(())); + } + + #[actix_rt::test] + async fn test_save_to_root_is_err() { + let sut = SUT::test(); + let file = file_in_dir(Path::new("/")); + let res = sut.save_to_file(file, contents()).await; + assert_eq!(res, Err(CommonError::Unknown)); + } + + #[actix_rt::test] + async fn test_delete_dir_does_not_work() { + let sut = SUT::test(); + let res = sut + .delete_file(String::from( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("target/") + .to_string_lossy(), + )) + .await; + assert_eq!(res, Err(CommonError::Unknown)); + } +} diff --git a/crates/clients/src/clients/client/file_system_client/mod.rs b/crates/clients/src/clients/client/file_system_client/mod.rs new file mode 100644 index 000000000..515604f32 --- /dev/null +++ b/crates/clients/src/clients/client/file_system_client/mod.rs @@ -0,0 +1,3 @@ +mod file_system_client; + +pub use file_system_client::*; diff --git a/crates/clients/src/clients/client/gateway_client/endpoints/mod.rs b/crates/clients/src/clients/client/gateway_client/endpoints/mod.rs new file mode 100644 index 000000000..eb625c37b --- /dev/null +++ b/crates/clients/src/clients/client/gateway_client/endpoints/mod.rs @@ -0,0 +1,2 @@ +mod state_endpoints; +mod transaction_endpoints; diff --git a/src/gateway_api/endpoints/state_endpoints.rs b/crates/clients/src/clients/client/gateway_client/endpoints/state_endpoints.rs similarity index 87% rename from src/gateway_api/endpoints/state_endpoints.rs rename to crates/clients/src/clients/client/gateway_client/endpoints/state_endpoints.rs index a5e3845a3..a3e229ce0 100644 --- a/src/gateway_api/endpoints/state_endpoints.rs +++ b/crates/clients/src/clients/client/gateway_client/endpoints/state_endpoints.rs @@ -2,7 +2,7 @@ use crate::prelude::*; impl GatewayClient { /// Returns detailed information for collection of entities. Aggregate resources globally by default. - pub(crate) async fn state_entity_details( + pub async fn state_entity_details( &self, request: StateEntityDetailsRequest, ) -> Result { diff --git a/src/gateway_api/endpoints/transaction_endpoints.rs b/crates/clients/src/clients/client/gateway_client/endpoints/transaction_endpoints.rs similarity index 90% rename from src/gateway_api/endpoints/transaction_endpoints.rs rename to crates/clients/src/clients/client/gateway_client/endpoints/transaction_endpoints.rs index 28b7a7b16..ae1190d62 100644 --- a/src/gateway_api/endpoints/transaction_endpoints.rs +++ b/crates/clients/src/clients/client/gateway_client/endpoints/transaction_endpoints.rs @@ -8,7 +8,7 @@ impl GatewayClient { /// See [the Gateway API docs for details][doc]. /// /// [doc]: https://radix-babylon-gateway-api.redoc.ly/#operation/TransactionConstruction - pub(crate) async fn transaction_construction(&self) -> Result { + pub async fn transaction_construction(&self) -> Result { self.post_empty( "transaction/construction", |response: TransactionConstructionResponse| { @@ -26,7 +26,7 @@ impl GatewayClient { /// See [the Gateway API docs for details][doc]. /// /// [doc]: https://radix-babylon-gateway-api.redoc.ly/#operation/TransactionPreview - pub(crate) async fn transaction_preview( + pub async fn transaction_preview( &self, request: TransactionPreviewRequest, ) -> Result { @@ -38,7 +38,7 @@ impl GatewayClient { /// See [the Gateway API docs for details][doc]. /// /// [doc]: https://radix-babylon-gateway-api.redoc.ly/#operation/TransactionSubmit - pub(crate) async fn transaction_submit( + pub async fn transaction_submit( &self, request: TransactionSubmitRequest, ) -> Result { diff --git a/src/gateway_api/client/gateway_client.rs b/crates/clients/src/clients/client/gateway_client/gateway_client.rs similarity index 79% rename from src/gateway_api/client/gateway_client.rs rename to crates/clients/src/clients/client/gateway_client/gateway_client.rs index abd74acea..45daadead 100644 --- a/src/gateway_api/client/gateway_client.rs +++ b/crates/clients/src/clients/client/gateway_client/gateway_client.rs @@ -18,20 +18,22 @@ pub struct GatewayClient { #[uniffi::export] impl GatewayClient { - /// Constructs a new `GatewayClient` with a NetworkAntenna for a specified + /// Constructs a new `GatewayClient` with a NetworkingDriver for a specified /// `Gateway`. #[uniffi::constructor] pub fn with_gateway( - network_antenna: Arc, + networking_driver: Arc, gateway: Gateway, ) -> Self { Self { - http_client: HttpClient { network_antenna }, + http_client: HttpClient { + driver: networking_driver, + }, gateway, } } - /// Constructs a new `GatewayClient` with a NetworkAntenna for a specified + /// Constructs a new `GatewayClient` with a NetworkingDriver for a specified /// network, by looking up an Radix DLT provided Gateway on that network. /// /// # Panics @@ -39,17 +41,10 @@ impl GatewayClient { /// `network_id` - e.g. will panic if you specify `NetworkID::Simulator` (duh). #[uniffi::constructor] pub fn new( - network_antenna: Arc, + networking_driver: Arc, network_id: NetworkID, ) -> Self { - Self::with_gateway(network_antenna, Gateway::from(network_id)) - } -} - -#[cfg(test)] -impl From<()> for BagOfBytes { - fn from(_value: ()) -> Self { - Self::new() + Self::with_gateway(networking_driver, Gateway::from(network_id)) } } @@ -57,7 +52,7 @@ impl From<()> for BagOfBytes { mod tests { use super::*; use actix_rt::time::timeout; - use reqwest::Response; + use std::time::Duration; const MAX: Duration = Duration::from_millis(10); diff --git a/src/gateway_api/client/gateway_client_dispatch_request.rs b/crates/clients/src/clients/client/gateway_client/gateway_client_dispatch_request.rs similarity index 69% rename from src/gateway_api/client/gateway_client_dispatch_request.rs rename to crates/clients/src/clients/client/gateway_client/gateway_client_dispatch_request.rs index 1c6fc18f8..df2a30ed6 100644 --- a/src/gateway_api/client/gateway_client_dispatch_request.rs +++ b/crates/clients/src/clients/client/gateway_client/gateway_client_dispatch_request.rs @@ -6,7 +6,7 @@ use crate::prelude::*; impl GatewayClient { /// Dispatches an HTTP `POST` request by JSON serializing the specified /// `request` and setting it as the `body` for the network request. - pub(crate) async fn post( + pub async fn post( &self, path: impl AsRef, request: T, @@ -38,7 +38,7 @@ impl GatewayClient { } /// Dispatches an HTTP `POST` request without any `body`. - pub(crate) async fn post_empty( + pub async fn post_empty( &self, path: impl AsRef, map: F, @@ -54,20 +54,6 @@ impl GatewayClient { } /// An identity mapping function for Result -pub(crate) const fn res_id(x: T) -> Result { +pub const fn res_id(x: T) -> Result { std::convert::identity::>(Ok(x)) } - -impl NetworkRequest { - fn with_gateway_api_headers(self) -> Self { - let headers = HashMap::::from_iter([ - ("content-Type".to_owned(), "application/json".to_owned()), - ("accept".to_owned(), "application/json".to_owned()), - ("user-agent".to_owned(), "Sargon".to_owned()), // https://stackoverflow.com/a/77866494/1311272 - ("RDX-Client-Name".to_owned(), "Sargon".to_owned()), - ("RDX-Client-Version".to_owned(), "1.5.1".to_owned()), - ]); - - self.with_headers(headers) - } -} diff --git a/src/gateway_api/methods/mod.rs b/crates/clients/src/clients/client/gateway_client/methods/mod.rs similarity index 100% rename from src/gateway_api/methods/mod.rs rename to crates/clients/src/clients/client/gateway_client/methods/mod.rs diff --git a/src/gateway_api/methods/state_methods.rs b/crates/clients/src/clients/client/gateway_client/methods/state_methods.rs similarity index 100% rename from src/gateway_api/methods/state_methods.rs rename to crates/clients/src/clients/client/gateway_client/methods/state_methods.rs diff --git a/src/gateway_api/methods/transaction_methods.rs b/crates/clients/src/clients/client/gateway_client/methods/transaction_methods.rs similarity index 100% rename from src/gateway_api/methods/transaction_methods.rs rename to crates/clients/src/clients/client/gateway_client/methods/transaction_methods.rs diff --git a/src/gateway_api/client/mod.rs b/crates/clients/src/clients/client/gateway_client/mod.rs similarity index 72% rename from src/gateway_api/client/mod.rs rename to crates/clients/src/clients/client/gateway_client/mod.rs index 5eaeec939..26fc0b488 100644 --- a/src/gateway_api/client/mod.rs +++ b/crates/clients/src/clients/client/gateway_client/mod.rs @@ -1,5 +1,8 @@ +mod endpoints; mod gateway_client; mod gateway_client_dispatch_request; +mod methods; pub use gateway_client::*; pub use gateway_client_dispatch_request::*; +pub use methods::*; diff --git a/crates/clients/src/clients/client/host_info_client/host_info_client.rs b/crates/clients/src/clients/client/host_info_client/host_info_client.rs new file mode 100644 index 000000000..a9de69ba7 --- /dev/null +++ b/crates/clients/src/clients/client/host_info_client/host_info_client.rs @@ -0,0 +1,73 @@ +use crate::prelude::*; + +#[derive(Debug)] +pub struct HostInfoClient { + driver: Arc, +} + +impl HostInfoClient { + pub fn new(driver: Arc) -> Self { + Self { driver } + } + + pub async fn summary(&self) -> String { + let host_model = self.driver.host_device_model().await; + let host_os_version = self.driver.host_device_system_version().await; + let host_app_version = self.driver.host_app_version().await; + let host_vendor = self.driver.host_device_vendor().await; + format!( + "App v{} running in host OS: {} on device: {} ({})", + host_app_version, host_os_version, host_model, host_vendor + ) + } + + pub async fn create_device_info(&self) -> DeviceInfo { + let host_name = self.driver.host_device_name().await; + let host_model = self.driver.host_device_model().await; + let host_os_version = self.driver.host_device_system_version().await; + let host_app_version = self.driver.host_app_version().await; + let host_vendor = self.driver.host_device_vendor().await; + + let maybe_device_id = self.driver.host_device_id().await; + + DeviceInfo::with_details( + maybe_device_id, + host_name, + host_model, + host_os_version, + host_app_version, + host_vendor, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = HostInfoClient; + + #[actix_rt::test] + async fn test_create_device_info() { + let sut = SUT::new(RustHostInfoDriver::new()); + let mut info = sut.create_device_info().await; + info.id = DeviceID::sample(); + info.date = Timestamp::sample(); + info.host_app_version = Some("0.0.0".to_string()); + pretty_assertions::assert_eq!( + info, + DeviceInfo::new( + DeviceID::sample(), + Timestamp::sample(), + DeviceInfoDescription::new( + "Rosebud", + "Rust Sargon Unknown Device Model" + ), + "macos", + "0.0.0", + "Rust Sargon Unknown Vendor" + ) + ); + } +} diff --git a/crates/clients/src/clients/client/host_info_client/mod.rs b/crates/clients/src/clients/client/host_info_client/mod.rs new file mode 100644 index 000000000..7971f493c --- /dev/null +++ b/crates/clients/src/clients/client/host_info_client/mod.rs @@ -0,0 +1,3 @@ +mod host_info_client; + +pub use host_info_client::*; diff --git a/src/http_client/client.rs b/crates/clients/src/clients/client/http_client/http_client.rs similarity index 76% rename from src/http_client/client.rs rename to crates/clients/src/clients/client/http_client/http_client.rs index 9fe1ed4f7..fbc176218 100644 --- a/src/http_client/client.rs +++ b/crates/clients/src/clients/client/http_client/http_client.rs @@ -1,18 +1,18 @@ use crate::prelude::*; -use serde_json::Value; /// A `HttpClient` needs a "network antenna" to be able to execute the /// network requests - which is a trait that clients implement on the FFI side (iOS/Android). +#[derive(Debug)] pub struct HttpClient { - /// An object implementing the `NetworkAntenna` traits, which iOS/Android + /// An object implementing the `NetworkingDriver` traits, which iOS/Android /// clients pass into the constructor of this GatewayClient, so that it can /// execute network requests. - pub network_antenna: Arc, + pub driver: Arc, } impl HttpClient { - pub fn new(network_antenna: Arc) -> Self { - Self { network_antenna } + pub fn new(driver: Arc) -> Self { + Self { driver } } } @@ -21,10 +21,7 @@ impl HttpClient { &self, request: NetworkRequest, ) -> Result { - let response = self - .network_antenna - .execute_network_request(request) - .await?; + let response = self.driver.execute_network_request(request).await?; // Check for valid status code if !(200..=299).contains(&response.status_code) { @@ -79,7 +76,8 @@ impl HttpClient { mod tests { use super::*; use actix_rt::time::timeout; - use reqwest::Response; + use drivers::*; + use std::time::Duration; const MAX: Duration = Duration::from_millis(10); @@ -89,7 +87,7 @@ mod tests { #[actix_rt::test] async fn execute_network_request_invalid_url() { - let mock_antenna = MockAntenna::new(200, ()); + let mock_antenna = MockAntenna::new(200, BagOfBytes::new()); let base = "http://example.com"; let sut = SUT::with_gateway( Arc::new(mock_antenna), @@ -109,7 +107,7 @@ mod tests { async fn execute_network_request_bad_status_code() { let mock_antenna = MockAntenna::new( 404, // bad code - (), + BagOfBytes::new(), ); let sut = SUT::new(Arc::new(mock_antenna), NetworkID::Stokenet); let req = sut.current_epoch(); @@ -136,25 +134,26 @@ mod tests { #[actix_rt::test] async fn spy_headers() { - let mock_antenna = MockAntenna::with_spy(200, (), |request| { - assert_eq!( - request - .headers - .keys() - .map(|v| v.to_string()) - .collect::>(), - [ - "RDX-Client-Version", - "RDX-Client-Name", - "accept", - "content-Type", - "user-agent" - ] - .into_iter() - .map(|s| s.to_owned()) - .collect::>() - ) - }); + let mock_antenna = + MockAntenna::with_spy(200, BagOfBytes::new(), |request| { + assert_eq!( + request + .headers + .keys() + .map(|v| v.to_string()) + .collect::>(), + [ + "RDX-Client-Version", + "RDX-Client-Name", + "accept", + "content-Type", + "user-agent" + ] + .into_iter() + .map(|s| s.to_owned()) + .collect::>() + ) + }); let sut = SUT::new(Arc::new(mock_antenna), NetworkID::Stokenet); let req = sut.current_epoch(); drop(timeout(MAX, req).await.unwrap()); diff --git a/crates/clients/src/clients/client/http_client/mod.rs b/crates/clients/src/clients/client/http_client/mod.rs new file mode 100644 index 000000000..73bcdca8d --- /dev/null +++ b/crates/clients/src/clients/client/http_client/mod.rs @@ -0,0 +1,3 @@ +mod http_client; + +pub use http_client::*; diff --git a/crates/clients/src/clients/client/mod.rs b/crates/clients/src/clients/client/mod.rs new file mode 100644 index 000000000..7f88d6321 --- /dev/null +++ b/crates/clients/src/clients/client/mod.rs @@ -0,0 +1,17 @@ +mod entropy_client; +mod event_bus_client; +mod file_system_client; +mod gateway_client; +mod host_info_client; +mod http_client; +mod secure_storage_client; +mod unsafe_storage_client; + +pub use entropy_client::*; +pub use event_bus_client::*; +pub use file_system_client::*; +pub use gateway_client::*; +pub use host_info_client::*; +pub use http_client::*; +pub use secure_storage_client::*; +pub use unsafe_storage_client::*; diff --git a/crates/clients/src/clients/client/secure_storage_client/mod.rs b/crates/clients/src/clients/client/secure_storage_client/mod.rs new file mode 100644 index 000000000..5efb3fcc7 --- /dev/null +++ b/crates/clients/src/clients/client/secure_storage_client/mod.rs @@ -0,0 +1,3 @@ +mod secure_storage_client; + +pub use secure_storage_client::*; diff --git a/crates/clients/src/clients/client/secure_storage_client/secure_storage_client.rs b/crates/clients/src/clients/client/secure_storage_client/secure_storage_client.rs new file mode 100644 index 000000000..8b5d2c46f --- /dev/null +++ b/crates/clients/src/clients/client/secure_storage_client/secure_storage_client.rs @@ -0,0 +1,485 @@ +use crate::prelude::*; + +/// An abstraction of an implementing WalletClients's secure storage, used by `Wallet` to +/// save and load models, most prominently `Profile` and `MnemonicWithPassphrase`. +/// +/// It uses the lower level CRUD trait `SecureStorageDriver` which works on bytes (Vec), +/// by instead working with JSON. +/// +/// The typical usage is that `Wallet` uses this to build even higher level API's that work +/// with application level types such as `PrivateHierarchicalDeterministicFactorSource`, which +/// apart from `MnemonicWithPassphrase` read from SecureStorageDriver using this `SecureStorageClient`, +/// also has to load the DeviceFactorSource from Profile, given a FactorSourceID only. +#[derive(Debug)] +pub struct SecureStorageClient { + /// Low level CRUD traits injected from implementing Wallet Client, that works on bytes. + driver: Arc, +} + +impl SecureStorageClient { + /// Creates a new SecureStorageClient using an implementation of + /// `SecureStorageDriver`. + pub fn new(driver: Arc) -> Self { + Self { driver } + } +} + +impl SecureStorageClient { + //====== + // Save T + //====== + pub async fn save(&self, key: SecureStorageKey, value: &T) -> Result<()> + where + T: serde::Serialize, + { + let json = serde_json::to_vec(value) + .map_err(|_| CommonError::FailedToSerializeToJSON)?; + self.driver + .save_data(key, BagOfBytes::from(json)) + // tarpaulin will incorrectly flag next line is missed + .await + } + + //====== + // Load T + //====== + /// Loads bytes from SecureStorageDriver and deserializes them into `T`. + /// + /// Returns `Ok(None)` if no bytes were found, returns Err if failed + /// to load bytes or failed to deserialize the JSON into a `T`. + pub async fn load(&self, key: SecureStorageKey) -> Result> + where + T: for<'a> serde::Deserialize<'a>, + { + self.driver.load_data(key).await.and_then(|o| match o { + None => Ok(None), + Some(j) => serde_json::from_slice(j.as_slice()).map_err(|_| { + let type_name = type_name::(); + error!( + "Deserialize json to type: {}\nJSON (utf8):\n{:?}", + &type_name, + String::from_utf8(j.to_vec()) + ); + CommonError::FailedToDeserializeJSONToValue { + json_byte_count: j.len() as u64, + type_name, + } + }), + }) + } + + /// Loads bytes from SecureStorageDriver and deserializes them into `T`. + /// + /// Returns Err if failed to load bytes or failed to deserialize the JSON into a `T`, + /// unlike `load` this method returns an error if `None` bytes were found. + pub async fn load_or( + &self, + key: SecureStorageKey, + err: CommonError, + ) -> Result + where + T: for<'a> serde::Deserialize<'a>, + { + self.load(key).await.and_then(|o| o.ok_or(err)) + } + + /// Loads bytes from SecureStorageDriver and deserializes them into `T`. + /// + /// Returns Err if failed to load bytes or failed to deserialize the JSON into a `T`, + /// unlike `load` this method returns `default` if `None` bytes were found. + pub async fn load_unwrap_or( + &self, + key: SecureStorageKey, + default: T, + ) -> T + where + T: for<'a> serde::Deserialize<'a> + Clone, + { + self.load(key) + .await + .map(|o| o.unwrap_or(default.clone())) + .unwrap_or(default) + } + + //====== + // Profile CR(U)D + //====== + + /// Loads the active Profile if any, by first loading the active + /// profile id. + pub async fn load_active_profile(&self) -> Result> { + debug!("Loading active profile"); + let Some(id) = self.load_active_profile_id().await? else { + trace!("Found no active profile id"); + return Ok(None); + }; + self.load_profile_with_id(id).await + } + + /// Loads the active Profile if any, by first loading the active + /// profile id. + pub async fn load_profile_with_id( + &self, + profile_id: ProfileID, + ) -> Result> { + debug!("Loading profile profile with id: {}", profile_id); + self.load_or( + SecureStorageKey::ProfileSnapshot { profile_id }, + CommonError::UnableToLoadProfileFromSecureStorage { + profile_id: profile_id.to_string(), + }, + ) + .await + .inspect(|_| debug!("Loaded profile")) + .inspect_err(|e| error!("Failed to load profile, error {e}")) + } + + /// Loads the active ProfileID if any + pub async fn load_active_profile_id(&self) -> Result> { + trace!("Loading active profile id"); + self.load(SecureStorageKey::ActiveProfileID).await + } + + /// Save `profile` and saves its id as active profile id + pub async fn save_profile_and_active_profile_id( + &self, + profile: &Profile, + ) -> Result<()> { + debug!( + "Saving profile, id: {}, and setting it as active", + &profile.id() + ); + self.save_profile(profile).await?; + self.save_active_profile_id(profile.id()).await + } + + /// Save `profile` + pub async fn save_profile(&self, profile: &Profile) -> Result<()> { + let profile_id = profile.id(); + debug!("Saving profile with id: {}", profile_id); + self.save(SecureStorageKey::ProfileSnapshot { profile_id }, profile) + .await + .inspect(|_| debug!("Saved profile with id {}", profile_id)) + .inspect_err(|e| error!("Failed to save profile, error {e}")) + } + + /// Save `profile_id` as the active profile id + pub async fn save_active_profile_id( + &self, + profile_id: ProfileID, + ) -> Result<()> { + debug!("Saving active profile id: {}", profile_id); + self.save(SecureStorageKey::ActiveProfileID, &profile_id) + .await + .inspect(|_| debug!("Saved active profile id")) + .inspect_err(|e| { + error!("Failed to save active profile id, error {e}") + }) + } + + //====== + // DeviceInfo CR(U)D + //====== + + /// Loads the DeviceInfo if any + pub async fn load_device_info(&self) -> Result> { + trace!("Loading device info"); + self.load(SecureStorageKey::DeviceInfo).await + } + + /// Saves [`DeviceInfo`] + pub async fn save_device_info( + &self, + device_info: &DeviceInfo, + ) -> Result<()> { + debug!("Saving new device info: {:?}", device_info); + self.save(SecureStorageKey::DeviceInfo, device_info) + .await + .inspect(|_| debug!("Saved new device info.")) + .map_err(|e| { + error!( + "Failed to save device info to secure storage - error {e}", + ); + CommonError::UnableToSaveDeviceInfoToSecureStorage + }) + } + + //====== + // Mnemonic CR(U)D + //====== + + /// Saves the MnemonicWithPassphrase of the private hd factor source + pub async fn save_private_hd_factor_source( + &self, + private_hd_factor_source: &PrivateHierarchicalDeterministicFactorSource, + ) -> Result<()> { + self.save_mnemonic_with_passphrase( + &private_hd_factor_source.mnemonic_with_passphrase, + &private_hd_factor_source.factor_source.id, + ) + .await + } + + /// Saves a MnemonicWithPassphrase under a given `FactorSourceIDFromHash` + pub async fn save_mnemonic_with_passphrase( + &self, + mnemonic_with_passphrase: &MnemonicWithPassphrase, + id: &FactorSourceIDFromHash, + ) -> Result<()> { + self.save( + SecureStorageKey::DeviceFactorSourceMnemonic { + factor_source_id: *id, + }, + mnemonic_with_passphrase, + ) + .await + .map_err(|_| { + CommonError::UnableToSaveMnemonicToSecureStorage { + bad_value: id.to_string(), + } + }) + } + + /// Loads a MnemonicWithPassphrase with a `FactorSourceIDFromHash` + pub async fn load_mnemonic_with_passphrase( + &self, + id: &FactorSourceIDFromHash, + ) -> Result { + self.load_or( + SecureStorageKey::DeviceFactorSourceMnemonic { + factor_source_id: *id, + }, + CommonError::UnableToLoadMnemonicFromSecureStorage { + bad_value: id.to_string(), + }, + ) + .await + } + + /// Deletes a MnemonicWithPassphrase with a `FactorSourceIDFromHash` + pub async fn delete_mnemonic( + &self, + id: &FactorSourceIDFromHash, + ) -> Result<()> { + self.driver + .delete_data_for_key(SecureStorageKey::DeviceFactorSourceMnemonic { + factor_source_id: *id, + }) + .await + } + + pub async fn delete_profile(&self, id: ProfileID) -> Result<()> { + warn!("Deleting profile with id: {}", id); + self.driver + .delete_data_for_key(SecureStorageKey::ProfileSnapshot { + profile_id: id, + }) + .await + } + + pub async fn delete_active_profile_id(&self) -> Result<()> { + warn!("Deleting active profile id"); + self.driver + .delete_data_for_key(SecureStorageKey::ActiveProfileID) + .await + } +} + +// FIXME: we want this to be `#[cfg(test)]`, but does not work across crates.. +impl SecureStorageClient { + pub fn ephemeral() -> (SecureStorageClient, Arc) { + let storage = EphemeralSecureStorage::new(); + (SecureStorageClient::new(storage.clone()), storage) + } + + pub fn always_fail() -> Self { + SecureStorageClient::new(Arc::new(AlwaysFailSecureStorage {})) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_sut() -> SecureStorageClient { + SecureStorageClient::ephemeral().0 + } + + #[actix_rt::test] + async fn load_ok_when_none() { + let sut = make_sut(); + assert_eq!( + sut.load::(SecureStorageKey::ActiveProfileID).await, + Ok(None) + ); + } + + #[actix_rt::test] + async fn load_fail_to_deserialize_json() { + let sut = make_sut(); + + assert!(sut + .save( + SecureStorageKey::ActiveProfileID, + &0u8, // obviously a u8 is not a Profile + ) + .await + .is_ok()); + assert_eq!( + sut.load::(SecureStorageKey::ActiveProfileID).await, + Err(CommonError::FailedToDeserializeJSONToValue { + json_byte_count: 1, + type_name: "Profile".to_string() + }) + ); + } + + #[actix_rt::test] + async fn load_successful() { + let sut = make_sut(); + + assert!(sut + .save(SecureStorageKey::ActiveProfileID, &Profile::sample()) + .await + .is_ok()); + assert_eq!( + sut.load::(SecureStorageKey::ActiveProfileID).await, + Ok(Some(Profile::sample())) + ); + } + + #[actix_rt::test] + async fn load_unwrap_or_some_default_not_used() { + let sut = make_sut(); + + assert!(sut + .save(SecureStorageKey::ActiveProfileID, &Profile::sample()) + .await + .is_ok()); + assert_eq!( + sut.load_unwrap_or::( + SecureStorageKey::ActiveProfileID, + Profile::sample_other() + ) + .await, + Profile::sample() + ); + } + + #[actix_rt::test] + async fn load_unwrap_or_none_default_is_used() { + let sut = make_sut(); + + assert_eq!( + sut.load_unwrap_or::( + SecureStorageKey::ActiveProfileID, + Profile::sample_other() + ) + .await, + Profile::sample_other() + ); + } + + #[actix_rt::test] + async fn save_mnemonic_with_passphrase() { + let private = + PrivateHierarchicalDeterministicFactorSource::sample_other(); + let factor_source_id = private.factor_source.id; + let (sut, storage) = SecureStorageClient::ephemeral(); + let key = + SecureStorageKey::DeviceFactorSourceMnemonic { factor_source_id }; + assert_eq!(storage.load_data(key.clone()).await, Ok(None)); // not yet saved + assert!(sut + .save_mnemonic_with_passphrase( + &private.mnemonic_with_passphrase, + &factor_source_id.clone() + ) + .await + .is_ok()); + + // Assert indeed was saved. + assert!(storage + .load_data(key) + .await + .map(|b| String::from_utf8(b.unwrap().to_vec()).unwrap()) + .unwrap() + .contains("zoo")); + } + + #[actix_rt::test] + async fn save_mnemonic_with_passphrase_failure() { + let sut = SecureStorageClient::always_fail(); + let id = FactorSourceIDFromHash::sample(); + assert_eq!( + sut.save_mnemonic_with_passphrase( + &MnemonicWithPassphrase::sample(), + &id + ) + .await, + Err(CommonError::UnableToSaveMnemonicToSecureStorage { + bad_value: id.to_string() + }) + ); + } + + #[actix_rt::test] + async fn delete_mnemonic() { + // ARRANGE + let private = + PrivateHierarchicalDeterministicFactorSource::sample_other(); + let factor_source_id = private.factor_source.id; + let (sut, storage) = SecureStorageClient::ephemeral(); + let key = + SecureStorageKey::DeviceFactorSourceMnemonic { factor_source_id }; + assert!(storage + .save_data(key.clone(), BagOfBytes::from(vec![0xde, 0xad])) + .await + .is_ok()); + assert_eq!( + storage.load_data(key.clone()).await, + Ok(Some(BagOfBytes::from(vec![0xde, 0xad]))) + ); // assert save worked + + // ACT + assert!(sut.delete_mnemonic(&factor_source_id).await.is_ok()); + + // ASSERT + assert_eq!(storage.load_data(key).await, Ok(None)); + } + + #[actix_rt::test] + async fn save_fail_to_serialize() { + use serde::Serialize; + struct AlwaysFailSerialize {} + impl Serialize for AlwaysFailSerialize { + fn serialize( + &self, + _serializer: S, + ) -> core::result::Result + where + S: Serializer, + { + Err(serde::ser::Error::custom(CommonError::Unknown)) + } + } + + let (sut, _) = SecureStorageClient::ephemeral(); + assert_eq!( + sut.save( + SecureStorageKey::ActiveProfileID, + &AlwaysFailSerialize {} + ) + .await, + Err(CommonError::FailedToSerializeToJSON) + ); + } + + #[actix_rt::test] + async fn save_fail_save_device_info() { + let sut = SecureStorageClient::always_fail(); + assert_eq!( + sut.save_device_info(&DeviceInfo::sample()).await, + Err(CommonError::UnableToSaveDeviceInfoToSecureStorage) + ); + } +} diff --git a/crates/clients/src/clients/client/unsafe_storage_client/mod.rs b/crates/clients/src/clients/client/unsafe_storage_client/mod.rs new file mode 100644 index 000000000..3bde2361c --- /dev/null +++ b/crates/clients/src/clients/client/unsafe_storage_client/mod.rs @@ -0,0 +1,3 @@ +mod unsafe_storage_client; + +pub use unsafe_storage_client::*; diff --git a/crates/clients/src/clients/client/unsafe_storage_client/unsafe_storage_client.rs b/crates/clients/src/clients/client/unsafe_storage_client/unsafe_storage_client.rs new file mode 100644 index 000000000..1547dbaf3 --- /dev/null +++ b/crates/clients/src/clients/client/unsafe_storage_client/unsafe_storage_client.rs @@ -0,0 +1,13 @@ +use crate::prelude::*; + +#[derive(Debug)] +pub struct UnsafeStorageClient { + #[allow(dead_code)] + driver: Arc, +} + +impl UnsafeStorageClient { + pub fn new(driver: Arc) -> Self { + Self { driver } + } +} diff --git a/crates/clients/src/clients/clients.rs b/crates/clients/src/clients/clients.rs new file mode 100644 index 000000000..e8710f99d --- /dev/null +++ b/crates/clients/src/clients/clients.rs @@ -0,0 +1,37 @@ +use crate::prelude::*; + +#[derive(Debug)] +pub struct Clients { + pub drivers: Arc, + pub host: HostInfoClient, + pub secure_storage: SecureStorageClient, + pub entropy: EntropyClient, + pub http_client: HttpClient, + pub unsafe_storage: UnsafeStorageClient, + pub file_system: FileSystemClient, + pub event_bus: EventBusClient, +} + +impl Clients { + pub fn with_drivers(drivers: Arc) -> Self { + let host = HostInfoClient::new(drivers.host_info.clone()); + let secure_storage = + SecureStorageClient::new(drivers.secure_storage.clone()); + let entropy = EntropyClient::new(drivers.entropy_provider.clone()); + let http_client = HttpClient::new(drivers.networking.clone()); + let unsafe_storage = + UnsafeStorageClient::new(drivers.unsafe_storage.clone()); + let file_system = FileSystemClient::new(drivers.file_system.clone()); + let event_bus = EventBusClient::new(drivers.event_bus.clone()); + Self { + drivers, + host, + secure_storage, + entropy, + http_client, + unsafe_storage, + file_system, + event_bus, + } + } +} diff --git a/crates/clients/src/clients/mod.rs b/crates/clients/src/clients/mod.rs new file mode 100644 index 000000000..75dc24407 --- /dev/null +++ b/crates/clients/src/clients/mod.rs @@ -0,0 +1,5 @@ +mod client; +mod clients; + +pub use client::*; +pub use clients::*; diff --git a/crates/clients/src/lib.rs b/crates/clients/src/lib.rs new file mode 100644 index 000000000..0392f4d6c --- /dev/null +++ b/crates/clients/src/lib.rs @@ -0,0 +1,18 @@ +mod clients; + +uniffi::remote_type!(Uuid, sargoncommon); +uniffi::remote_type!(Timestamp, sargoncommon); +uniffi::remote_type!(Url, sargoncommon); + +pub mod prelude { + + pub use crate::clients::*; + + pub use drivers::prelude::*; + pub use gateway_models::prelude::*; + pub use sargoncommon::prelude::Result; +} + +pub use prelude::*; + +uniffi::include_scaffolding!("clients"); diff --git a/crates/clients/uniffi.toml b/crates/clients/uniffi.toml new file mode 100644 index 000000000..96e17f8e8 --- /dev/null +++ b/crates/clients/uniffi.toml @@ -0,0 +1,8 @@ +namespace = "clients" + +[bindings.swift] +module_name = "SargonClients" +experimental_sendable_value_types = true + +[bindings.kotlin] +package_name = "com.radixdlt.sargon.clients" diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml new file mode 100644 index 000000000..fcd243d08 --- /dev/null +++ b/crates/common/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "sargoncommon" +version = "1.1.0" +edition = "2021" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } +thiserror = { workspace = true } +derive_more = { workspace = true } +rand = { workspace = true } +zeroize = { workspace = true } +log = { workspace = true } +pretty_assertions = { workspace = true } +aes-gcm = { workspace = true } +url = { workspace = true } +delegate = { workspace = true } +hkdf = { workspace = true } +reqwest = { workspace = true } +actix-rt = { workspace = true } +async-trait = { workspace = true } +assert-json-diff = { workspace = true } +uuid = { workspace = true } +strum = { workspace = true } +hex = { workspace = true } +enum-iterator = { workspace = true } +itertools = { workspace = true } +enum-as-inner = { workspace = true } +pretty_env_logger = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +serde_with = { workspace = true } +serde_repr = { workspace = true } +iso8601-timestamp = { workspace = true } +paste = { workspace = true } +iota-crypto = { workspace = true } +k256 = { workspace = true } + +sbor = { workspace = true } +radix-rust = { workspace = true } +radix-engine = { workspace = true } +radix-common = { workspace = true } +radix-common-derive = { workspace = true } +radix-engine-interface = { workspace = true } +radix-transactions = { workspace = true } +radix-engine-toolkit-json = { workspace = true } +radix-engine-toolkit = { workspace = true } + + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } + +[lib] +crate-type = ["staticlib", "cdylib", "lib"] diff --git a/crates/common/build.rs b/crates/common/build.rs new file mode 100644 index 000000000..6373e8647 --- /dev/null +++ b/crates/common/build.rs @@ -0,0 +1,4 @@ +pub fn main() { + uniffi::generate_scaffolding("src/sargoncommon.udl") + .expect("Should be able to build."); +} diff --git a/src/core/assert_json.rs b/crates/common/src/assert_json.rs similarity index 100% rename from src/core/assert_json.rs rename to crates/common/src/assert_json.rs diff --git a/src/core/error/common_error.rs b/crates/common/src/error/common_error.rs similarity index 87% rename from src/core/error/common_error.rs rename to crates/common/src/error/common_error.rs index 32f9daa75..d5f29bf7a 100644 --- a/src/core/error/common_error.rs +++ b/crates/common/src/error/common_error.rs @@ -91,10 +91,7 @@ pub enum CommonError { InvalidEntityKind { bad_value: u32 } = 10023, #[error("Wrong entity kind, (expected {expected}, found {found})")] - WrongEntityKind { - expected: CAP26EntityKind, - found: CAP26EntityKind, - } = 10024, + WrongEntityKind { expected: u32, found: u32 } = 10024, #[error( "InvalidKeyKind, got: '{bad_value}', expected any of: [1460H, 1678H, 1391H]." @@ -191,10 +188,7 @@ pub enum CommonError { #[error( "Accounts on different networks, expected: {expected}, found: {found}" )] - AccountOnWrongNetwork { - expected: NetworkID, - found: NetworkID, - } = 10053, + AccountOnWrongNetwork { expected: u8, found: u8 } = 10053, #[error("FactorSources must not be empty.")] FactorSourcesMustNotBeEmpty = 10054, @@ -203,10 +197,7 @@ pub enum CommonError { UpdateFactorSourceMutateFailed = 10055, #[error("Failed to cast factor source, wrong kind, , expected: {expected}, found: {found}")] - CastFactorSourceWrongKind { - expected: FactorSourceKind, - found: FactorSourceKind, - } = 10056, + CastFactorSourceWrongKind { expected: String, found: String } = 10056, #[error("Length check failed, expected: {expected}, found: {found}, data: {data:?}")] InvalidLength { @@ -264,33 +255,30 @@ pub enum CommonError { FailedToLoadProfileHeadersList = 10072, #[error("FactorSource with ID not found in Profile: {bad_value:?}")] - ProfileDoesNotContainFactorSourceWithID { bad_value: FactorSourceID } = - 10073, + ProfileDoesNotContainFactorSourceWithID { bad_value: String } = 10073, - #[error("No active ProfileID found in SecureStorage.")] + #[error("No active ProfileID found in SecureStorageDriver.")] NoActiveProfileIDSet = 10074, #[error("No Profile snapshot found for ProfileID {bad_value}")] - ProfileSnapshotNotFound { bad_value: ProfileID } = 10075, + ProfileSnapshotNotFound { bad_value: String } = 10075, #[error("Account Already Present {bad_value}")] - AccountAlreadyPresent { bad_value: AccountAddress } = 10076, + AccountAlreadyPresent { bad_value: String } = 10076, #[error("Unable to acquire write lock for Profile inside Wallet")] UnableToAcquireWriteLockForProfile = 10077, - #[error("Failed save Mnemonic to SecureStorage with FactorSourceID: {bad_value}")] - UnableToSaveMnemonicToSecureStorage { bad_value: FactorSourceIDFromHash } = - 10078, + #[error("Failed save Mnemonic to SecureStorageDriver with FactorSourceID: {bad_value}")] + UnableToSaveMnemonicToSecureStorage { bad_value: String } = 10078, #[error( - "Failed load Mnemonic from SecureStorage with FactorSourceID: {bad_value}" + "Failed load Mnemonic from SecureStorageDriver with FactorSourceID: {bad_value}" )] - UnableToLoadMnemonicFromSecureStorage { bad_value: FactorSourceIDFromHash } = - 10079, + UnableToLoadMnemonicFromSecureStorage { bad_value: String } = 10079, - #[error("Failed save FactorSource to SecureStorage, FactorSourceID: {bad_value}")] - UnableToSaveFactorSourceToProfile { bad_value: FactorSourceID } = 10080, + #[error("Failed save FactorSource to SecureStorageDriver, FactorSourceID: {bad_value}")] + UnableToSaveFactorSourceToProfile { bad_value: String } = 10080, #[error("Expected IdentityPath but got something else.")] ExpectedIdentityPathButGotSomethingElse = 10081, @@ -316,7 +304,7 @@ pub enum CommonError { #[error("Failed to create Address (via RetAddress) from node_id (hex): {node_id_as_hex}, network_id: {network_id}")] FailedToCreateAddressViaRetAddressFromNodeIdAndNetworkID { node_id_as_hex: String, - network_id: NetworkID, + network_id: u8, } = 10088, #[error("Invalid Olympia address string: {bad_value}")] @@ -343,8 +331,8 @@ pub enum CommonError { #[error("Invalid Manifest Instructions String, found network in instructions {found_in_instructions}, but specified to constructor: {specified_to_instructions_ctor}")] InvalidInstructionsWrongNetwork { - found_in_instructions: NetworkID, - specified_to_instructions_ctor: NetworkID, + found_in_instructions: u8, + specified_to_instructions_ctor: u8, } = 10095, #[error( @@ -465,19 +453,16 @@ pub enum CommonError { RadixMobileInvalidInteractionID { bad_value: String } = 10130, #[error("Network discrepancy, expected : {expected}, actual: {actual}")] - NetworkDiscrepancy { - expected: NetworkID, - actual: NetworkID, - } = 10131, + NetworkDiscrepancy { expected: u8, actual: u8 } = 10131, #[error("Discrepancy, Authorized Dapp references Persona which does not exist {address}")] DiscrepancyAuthorizedDappReferencedPersonaWhichDoesNotExist { - address: IdentityAddress, + address: String, } = 10132, #[error("Discrepancy, Authorized Dapp references Account which does not exist {address}")] DiscrepancyAuthorizedDappReferencedAccountWhichDoesNotExist { - address: AccountAddress, + address: String, } = 10133, #[error("AuthorizedDapp references field id that does not exist")] @@ -491,6 +476,44 @@ pub enum CommonError { #[error("Invalid RadixConnectPurpose, bad value: {bad_value}")] InvalidRadixConnectPurpose { bad_value: String } = 10137, + + #[error( + "Failed to load Profile from secure storage, profile id: {profile_id}" + )] + UnableToLoadProfileFromSecureStorage { profile_id: String } = 10138, + + #[error("Failed to save DeviceInfo to secure storage")] + UnableToSaveDeviceInfoToSecureStorage = 10139, + + #[error("Unable to acquire read lock for profile")] + UnableToAcquireReadLockForProfile = 10140, + + #[error("Failed to read from unsafe storage.")] + UnsafeStorageReadError = 10141, + + #[error("Failed to write to unsafe storage.")] + UnsafeStorageWriteError = 10142, + + #[error("Failed to create file path from string: '{bad_value}'")] + FailedToCreateFilePathFromString { bad_value: String } = 10143, + + #[error("Expected collection to not be empty")] + ExpectedNonEmptyCollection = 10144, + + #[error("Failed to add all accounts, found duplicated account.")] + UnableToAddAllAccountsDuplicatesFound = 10145, + + #[error("Profile last used on other device {other_device_id} (this device: {this_device_id})")] + ProfileLastUsedOnOtherDevice { + other_device_id: String, + this_device_id: String, + } = 10146, + + #[error("Failed To create DeviceID (UUID) from string: {bad_value}")] + InvalidDeviceID { bad_value: String } = 10147, + + #[error("Tried to replace profile with one with a different ProfileID than the current one. Use `import_profile` instead.")] + TriedToUpdateProfileWithOneWithDifferentID = 10148, } #[uniffi::export] @@ -500,7 +523,7 @@ pub fn error_message_from_error(error: &CommonError) -> String { impl CommonError { pub fn error_code(&self) -> u32 { - unsafe { *<*const _>::from(self).cast::() } + core::intrinsics::discriminant_value(self) } } diff --git a/src/core/error/mod.rs b/crates/common/src/error/mod.rs similarity index 100% rename from src/core/error/mod.rs rename to crates/common/src/error/mod.rs diff --git a/src/core/has_sample_values.rs b/crates/common/src/has_sample_values.rs similarity index 100% rename from src/core/has_sample_values.rs rename to crates/common/src/has_sample_values.rs diff --git a/src/core/hash.rs b/crates/common/src/hash.rs similarity index 72% rename from src/core/hash.rs rename to crates/common/src/hash.rs index c171cfbcd..a74b8a15c 100644 --- a/src/core/hash.rs +++ b/crates/common/src/hash.rs @@ -15,21 +15,13 @@ use crate::prelude::*; )] pub struct HashSecretMagic(ScryptoHash); -uniffi::custom_type!(HashSecretMagic, BagOfBytes); - -impl crate::UniffiCustomTypeConverter for HashSecretMagic { - type Builtin = BagOfBytes; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Exactly32Bytes::try_from(val.bytes) - .map(|e| HashSecretMagic(ScryptoHash::from_bytes(*e.bytes()))) - .map_err(|e| e.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { - BagOfBytes::from(obj.0.into_bytes().as_slice()) - } -} +uniffi::custom_type!(HashSecretMagic, BagOfBytes, { + remote, + from_custom: |secret_magic| BagOfBytes::from(secret_magic.0.into_bytes().as_slice()), + try_into_custom: |b| Exactly32Bytes::try_from(b.bytes) + .map(|e| HashSecretMagic(ScryptoHash::from_bytes(*e.bytes()))) + .map_err(|e| e.into()) +}); /// Represents a 32-byte hash digest. /// @@ -48,7 +40,7 @@ impl crate::UniffiCustomTypeConverter for HashSecretMagic { uniffi::Record, )] pub struct Hash { - pub(crate) secret_magic: HashSecretMagic, + pub secret_magic: HashSecretMagic, } impl AsRef for Hash { @@ -184,37 +176,37 @@ mod tests { ); } - #[test] - fn manual_perform_uniffi_conversion_successful() { - let sut = SUT::sample().secret_magic; - let builtin = BagOfBytes::from_hex( - "48f1bd08444b5e713db9e14caac2faae71836786ac94d645b00679728202a935", - ) - .unwrap(); - - let ffi_side = - ::from_custom( - sut, - ); - - assert_eq!(ffi_side.to_hex(), builtin.to_hex()); - - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - - assert_eq!(sut, from_ffi_side); - } - - #[test] - fn manual_perform_uniffi_conversion_fail() { - assert!( - ::into_custom( - BagOfBytes::from_hex("deadbeef").unwrap(), - ) - .is_err() - ); - } + // #[test] + // fn manual_perform_uniffi_conversion_successful() { + // let sut = SUT::sample().secret_magic; + // let builtin = BagOfBytes::from_hex( + // "48f1bd08444b5e713db9e14caac2faae71836786ac94d645b00679728202a935", + // ) + // .unwrap(); + + // let ffi_side = + // ::from_custom( + // sut, + // ); + + // assert_eq!(ffi_side.to_hex(), builtin.to_hex()); + + // let from_ffi_side = + // ::into_custom( + // ffi_side, + // ) + // .unwrap(); + + // assert_eq!(sut, from_ffi_side); + // } + + // #[test] + // fn manual_perform_uniffi_conversion_fail() { + // assert!( + // ::into_custom( + // BagOfBytes::from_hex("deadbeef").unwrap(), + // ) + // .is_err() + // ); + // } } diff --git a/src/core/hash_uniffi_fn.rs b/crates/common/src/hash_uniffi_fn.rs similarity index 100% rename from src/core/hash_uniffi_fn.rs rename to crates/common/src/hash_uniffi_fn.rs diff --git a/src/lib.rs b/crates/common/src/lib.rs similarity index 73% rename from src/lib.rs rename to crates/common/src/lib.rs index 97db3c999..6c546f4f6 100644 --- a/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,60 +1,65 @@ +#![feature(core_intrinsics)] #![allow(unused_imports)] +#![allow(internal_features)] -mod core; -mod gateway_api; -mod hierarchical_deterministic; -mod http_client; -mod profile; -mod radix_connect; -mod wallet; -mod wrapped_radix_engine_toolkit; +mod assert_json; +mod error; +mod has_sample_values; +mod hash; +mod hash_uniffi_fn; +mod secure_random_bytes; +mod types; +mod unsafe_id_stepper; +mod utils; pub mod prelude { - pub use crate::core::*; - pub use crate::gateway_api::*; - pub use crate::hierarchical_deterministic::*; - pub use crate::http_client::*; - pub use crate::profile::*; - pub use crate::radix_connect::*; - pub use crate::wallet::*; - pub use crate::wrapped_radix_engine_toolkit::*; + pub use crate::assert_json::*; + pub use crate::error::*; + pub use crate::has_sample_values::*; + pub use crate::hash::*; + pub use crate::hash_uniffi_fn::*; + pub use crate::secure_random_bytes::*; + pub use crate::types::*; + pub use crate::unsafe_id_stepper::*; + pub use crate::utils::*; - pub(crate) use radix_rust::prelude::{ + pub use radix_rust::prelude::{ BTreeSet, HashMap, HashSet, IndexMap, IndexSet, }; - pub(crate) use ::hex::decode as hex_decode; - pub(crate) use ::hex::encode as hex_encode; - pub(crate) use iso8601_timestamp::Timestamp; - pub(crate) use itertools::Itertools; - pub(crate) use log::{debug, error, info, trace, warn}; - pub(crate) use serde::{ + pub use hex::decode as hex_decode; + pub use hex::encode as hex_encode; + pub use iso8601_timestamp::Timestamp; + pub use itertools::Itertools; + pub use log::{debug, error, info, trace, warn}; + pub use paste::*; + pub use serde::{ de, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer, }; - pub(crate) use serde_json::json; - pub(crate) use serde_repr::{Deserialize_repr, Serialize_repr}; - pub(crate) use serde_with::*; - pub(crate) use zeroize::{Zeroize, ZeroizeOnDrop}; + pub use serde_json::json; + pub use serde_repr::{Deserialize_repr, Serialize_repr}; + pub use serde_with::*; + pub use zeroize::{Zeroize, ZeroizeOnDrop}; pub use radix_common::math::traits::CheckedMul as ScryptoCheckedMul; - pub(crate) use std::cmp::Ordering; - pub(crate) use std::collections::BTreeMap; - pub(crate) use std::fmt::{Debug, Display, Formatter}; - pub(crate) use std::fs; - pub(crate) use std::hash::Hash as StdHash; + pub use std::cmp::Ordering; + pub use std::collections::BTreeMap; + pub use std::fmt::{Debug, Display, Formatter}; + pub use std::fs; + pub use std::hash::Hash as StdHash; pub use std::ops::{Add, AddAssign, Deref, Div, Mul, Neg, Sub}; - pub(crate) use std::str::FromStr; - pub(crate) use std::sync::Arc; + pub use std::str::FromStr; + pub use std::sync::{Arc, RwLock}; - pub(crate) use strum::FromRepr; - pub(crate) use url::Url; - pub(crate) use uuid::Uuid; + pub use strum::FromRepr; + pub use url::Url; + pub use uuid::Uuid; - pub(crate) use enum_as_inner::EnumAsInner; - pub(crate) use paste::*; - pub(crate) use radix_engine::{ + pub use enum_as_inner::EnumAsInner; + pub use paste::*; + pub use radix_engine::{ blueprints::consensus_manager::UnstakeData as ScryptoUnstakeData, system::system_modules::execution_trace::ResourceSpecifier as ScryptoResourceSpecifier, transaction::{ @@ -63,9 +68,9 @@ pub mod prelude { VersionedTransactionReceipt as ScryptoVersionedTransactionReceipt, }, }; - pub(crate) use sbor::Versioned; + pub use sbor::Versioned; - pub(crate) use radix_common::{ + pub use radix_common::{ address::AddressBech32Encoder as ScryptoAddressBech32Encoder, crypto::{ blake2b_256_hash, verify_ed25519 as scrypto_verify_ed25519, @@ -116,14 +121,14 @@ pub mod prelude { }, ManifestSbor as ScryptoManifestSbor, ScryptoSbor, }; - pub(crate) use radix_engine_interface::blueprints::{ + pub use radix_engine_interface::blueprints::{ account::{ DefaultDepositRule as ScryptoDefaultDepositRule, ResourcePreference as ScryptoResourcePreference, }, resource::ResourceOrNonFungible as ScryptoResourceOrNonFungible, }; - pub(crate) use radix_engine_interface::prelude::{ + pub use radix_engine_interface::prelude::{ AccessRule as ScryptoAccessRule, Epoch as ScryptoEpoch, FungibleResourceRoles as ScryptoFungibleResourceRoles, MetadataInit as ScryptoMetadataInit, @@ -136,9 +141,9 @@ pub mod prelude { UncheckedUrl as ScryptoUncheckedUrl, }; - pub(crate) use enum_iterator::all; + pub use enum_iterator::all; - pub(crate) use radix_transactions::{ + pub use radix_transactions::{ builder::{ ExistingManifestBucket as ScryptoExistingManifestBucket, ManifestNameRegistrar as ScryptoManifestNameRegistrar, @@ -185,7 +190,7 @@ pub mod prelude { }, }; - pub(crate) use radix_engine_toolkit_json::models::{ + pub use radix_engine_toolkit_json::models::{ common::SerializableNonFungibleLocalId as RetNonFungibleLocalId, scrypto::non_fungible_global_id::{ SerializableNonFungibleGlobalId as RetNonFungibleGlobalId, @@ -242,56 +247,67 @@ pub mod prelude { }, }; } - pub use prelude::*; // Use `Url` as a custom type, with `String` as the Builtin -uniffi::custom_type!(Url, String); +uniffi::custom_type!(Url, String, { + remote, + from_custom: |url| url.to_string(), + try_into_custom: |val| Ok(Url::parse(&val)?) +}); -// Use `url::Url` as a custom type, with `String` as the Builtin -#[cfg(not(tarpaulin_include))] // Tested in binding tests (e.g. test*.swift files) -impl UniffiCustomTypeConverter for Url { - type Builtin = String; +// // Use `url::Url` as a custom type, with `String` as the Builtin +// #[cfg(not(tarpaulin_include))] // Tested in binding tests (e.g. test*.swift files) +// impl UniffiCustomTypeConverter for Url { +// type Builtin = String; - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(Url::parse(&val)?) - } +// fn into_custom(val: Self::Builtin) -> uniffi::Result { +// +// } - fn from_custom(obj: Self) -> Self::Builtin { - obj.into() - } -} +// fn from_custom(obj: Self) -> Self::Builtin { +// obj.into() +// } +// } // Use `Timestamp` as a custom type, with `String` as the Builtin -uniffi::custom_type!(Timestamp, String); +uniffi::custom_type!(Timestamp, String, { + remote, + from_custom: |time| time.to_string(), + try_into_custom: |val| Timestamp::parse(val.as_str()) + .ok_or(CommonError::InvalidISO8601String { bad_value: val }) + .map_err(|e| e.into()) +}); -#[cfg(not(tarpaulin_include))] // Tested in binding tests (e.g. test*.swift files) -impl UniffiCustomTypeConverter for Timestamp { - type Builtin = String; +// #[cfg(not(tarpaulin_include))] // Tested in binding tests (e.g. test*.swift files) +// impl UniffiCustomTypeConverter for Timestamp { +// type Builtin = String; - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Timestamp::parse(val.as_str()) - .ok_or(CommonError::InvalidISO8601String { bad_value: val }) - .map_err(|e| e.into()) - } +// fn into_custom(val: Self::Builtin) -> uniffi::Result { - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_string() - } -} +// } + +// fn from_custom(obj: Self) -> Self::Builtin { +// obj.to_string() +// } +// } // Use `Uuid` as a custom type, with `String` as the Builtin -uniffi::custom_type!(Uuid, String); +uniffi::custom_type!(Uuid, String, { + remote, + from_custom: |id| id.to_string(), + try_into_custom: |val| Uuid::try_parse(val.as_str()).map_err(|e| e.into()) +}); -#[cfg(not(tarpaulin_include))] // Tested in binding tests (e.g. test*.swift files) -impl UniffiCustomTypeConverter for Uuid { - type Builtin = String; - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Uuid::try_parse(val.as_str()).map_err(|e| e.into()) - } - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_string() - } -} +// #[cfg(not(tarpaulin_include))] // Tested in binding tests (e.g. test*.swift files) +// impl UniffiCustomTypeConverter for Uuid { +// type Builtin = String; +// fn into_custom(val: Self::Builtin) -> uniffi::Result { +// +// } +// fn from_custom(obj: Self) -> Self::Builtin { +// obj.to_string() +// } +// } -uniffi::include_scaffolding!("sargon"); +uniffi::include_scaffolding!("sargoncommon"); diff --git a/crates/common/src/sargoncommon.udl b/crates/common/src/sargoncommon.udl new file mode 100644 index 000000000..eafc142fa --- /dev/null +++ b/crates/common/src/sargoncommon.udl @@ -0,0 +1 @@ +namespace sargoncommon {}; diff --git a/src/core/secure_random_bytes.rs b/crates/common/src/secure_random_bytes.rs similarity index 100% rename from src/core/secure_random_bytes.rs rename to crates/common/src/secure_random_bytes.rs diff --git a/src/profile/v100/entity/abstract_entity_type.rs b/crates/common/src/types/abstract_entity_type.rs similarity index 100% rename from src/profile/v100/entity/abstract_entity_type.rs rename to crates/common/src/types/abstract_entity_type.rs diff --git a/src/core/types/appearance_id.rs b/crates/common/src/types/appearance_id.rs similarity index 79% rename from src/core/types/appearance_id.rs rename to crates/common/src/types/appearance_id.rs index 5dfad9c95..8dd3c4a6f 100644 --- a/src/core/types/appearance_id.rs +++ b/crates/common/src/types/appearance_id.rs @@ -39,50 +39,18 @@ impl AppearanceID { fn declare(value: u8) -> Self { Self::new(value).expect("Should have declared valid value.") } - pub fn gradient0() -> Self { - Self::declare(0) - } - pub fn gradient1() -> Self { - Self::declare(1) - } - pub fn gradient2() -> Self { - Self::declare(2) - } - pub fn gradient3() -> Self { - Self::declare(3) - } - pub fn gradient4() -> Self { - Self::declare(4) - } - pub fn gradient5() -> Self { - Self::declare(5) - } - pub fn gradient6() -> Self { - Self::declare(6) - } - pub fn gradient7() -> Self { - Self::declare(7) - } - pub fn gradient8() -> Self { - Self::declare(8) - } - pub fn gradient9() -> Self { - Self::declare(9) - } - pub fn gradient10() -> Self { - Self::declare(10) - } - pub fn gradient11() -> Self { - Self::declare(11) + + pub fn all() -> Vec { + (0..=Self::MAX).map(Self::declare).collect_vec() } } impl HasSampleValues for AppearanceID { fn sample() -> Self { - Self::gradient0() + *Self::all().first().unwrap() } fn sample_other() -> Self { - Self::gradient11() + *Self::all().iter().last().unwrap() } } @@ -124,6 +92,11 @@ mod tests { assert_ne!(SUT::sample(), SUT::sample_other()); } + #[test] + fn len_of_all_is_max_plus_one() { + assert_eq!(SUT::all().len(), SUT::MAX as usize + 1); + } + #[test] fn test_from_number_of_accounts() { assert_eq!(SUT::from_number_of_accounts_on_network(12), SUT::sample()); diff --git a/src/core/types/appearance_id_uniffi_fn.rs b/crates/common/src/types/appearance_id_uniffi_fn.rs similarity index 69% rename from src/core/types/appearance_id_uniffi_fn.rs rename to crates/common/src/types/appearance_id_uniffi_fn.rs index 739bcfbf0..fc86a11c2 100644 --- a/src/core/types/appearance_id_uniffi_fn.rs +++ b/crates/common/src/types/appearance_id_uniffi_fn.rs @@ -24,20 +24,7 @@ pub fn new_appearance_id_sample_other() -> AppearanceID { #[uniffi::export] pub fn appearance_ids_all() -> Vec { - vec![ - AppearanceID::gradient0(), - AppearanceID::gradient1(), - AppearanceID::gradient2(), - AppearanceID::gradient3(), - AppearanceID::gradient4(), - AppearanceID::gradient5(), - AppearanceID::gradient6(), - AppearanceID::gradient7(), - AppearanceID::gradient8(), - AppearanceID::gradient9(), - AppearanceID::gradient10(), - AppearanceID::gradient11(), - ] + AppearanceID::all() } #[cfg(test)] @@ -49,7 +36,7 @@ mod uniffi_tests { #[test] fn new() { - assert_eq!(new_appearance_id(5).unwrap(), SUT::gradient5()); + assert_eq!(new_appearance_id(5).unwrap(), SUT::new(5).unwrap()); } #[test] diff --git a/src/core/types/bag_of_bytes.rs b/crates/common/src/types/bag_of_bytes.rs similarity index 85% rename from src/core/types/bag_of_bytes.rs rename to crates/common/src/types/bag_of_bytes.rs index 75adf917a..63a5d5cf4 100644 --- a/src/core/types/bag_of_bytes.rs +++ b/crates/common/src/types/bag_of_bytes.rs @@ -24,7 +24,7 @@ use crate::prelude::*; #[display("{}", self.to_hex())] #[debug("{}", self.to_hex())] pub struct BagOfBytes { - pub(crate) bytes: Vec, + pub bytes: Vec, } impl AsRef<[u8]> for BagOfBytes { @@ -52,44 +52,32 @@ impl DerefMut for BagOfBytes { } } -/// Expose `BagOfBytes` to Uniffi as `sequence`, unfortunately we cannot -/// use `sequence` because it results in: -/// -/// /uniffi-rs-6f89edd2a1ffa4bd/fb8dd5c/uniffi_bindgen/src/interface/universe.rs:50:17: -/// assertion `left == right` failed -/// left: Custom { module_path: "profile", name: "BagOfBytes", builtin: Bytes } -/// right: Custom { module_path: "profile", name: "BagOfBytes", builtin: Sequence { inner_type: UInt8 } } -/// -/// So HACK HACK HACK we use `sequence` (`Vec`) instead as an intermediary `Builtin`. -/// -/// However, in `uniffi.toml` we provide `from_custom`` / `into_custom`` for Kotlin and Swift -/// which using two's complement maps back Vec -> Vec, meaning Kotlin and Swift actually -/// never see the `i8`, and only works with u8. -/// -/// So we translate: -/// Kotlin: `Rust[BagOfBytes <:2's comp.:> Vec] <:2's comp:> [Kotlin]List` -/// Swift: `Rust[BagOfBytes <:2's comp.:> Vec] <:2's comp:> [Swift]Foundation.Data` -/// -impl crate::UniffiCustomTypeConverter for BagOfBytes { - type Builtin = Vec; - - #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(val - .into_iter() - .map(twos_complement_of_i8) - .collect_vec() - .into()) - } - - #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_vec() - .into_iter() - .map(twos_complement_of_u8) - .collect_vec() - } -} +/* + Expose `BagOfBytes` to Uniffi as `sequence`, unfortunately we cannot + use `sequence` because it results in: + /uniffi-rs-6f89edd2a1ffa4bd/fb8dd5c/uniffi_bindgen/src/interface/universe.rs:50:17: + assertion `left == right` failed + left: Custom { module_path: "profile", name: "BagOfBytes", builtin: Bytes } + right: Custom { module_path: "profile", name: "BagOfBytes", builtin: Sequence { inner_type: UInt8 } } + So HACK HACK HACK we use `sequence` (`Vec`) instead as an intermediary `Builtin`. + However, in `uniffi.toml` we provide `from_custom`` / `into_custom`` for Kotlin and Swift + which using two's complement maps back Vec -> Vec, meaning Kotlin and Swift actually + never see the `i8`, and only works with u8. + So we translate: + Kotlin: `Rust[BagOfBytes <:2's comp.:> Vec] <:2's comp:> [Kotlin]List` + Swift: `Rust[BagOfBytes <:2's comp.:> Vec] <:2's comp:> [Swift]Foundation.Data` +*/ +uniffi::custom_type!(BagOfBytes, Vec, { + from_custom: |s| s.to_vec() + .into_iter() + .map(twos_complement_of_u8) + .collect_vec(), + try_into_custom: |s| Ok(s + .into_iter() + .map(twos_complement_of_i8) + .collect_vec() + .into()), +}); impl BagOfBytes { pub fn new() -> Self { diff --git a/src/core/types/bag_of_bytes_uniffi_fn.rs b/crates/common/src/types/bag_of_bytes_uniffi_fn.rs similarity index 100% rename from src/core/types/bag_of_bytes_uniffi_fn.rs rename to crates/common/src/types/bag_of_bytes_uniffi_fn.rs diff --git a/src/core/types/decimal192.rs b/crates/common/src/types/decimal192.rs similarity index 98% rename from src/core/types/decimal192.rs rename to crates/common/src/types/decimal192.rs index fa8a55c7e..ec8f8d816 100644 --- a/src/core/types/decimal192.rs +++ b/crates/common/src/types/decimal192.rs @@ -2,22 +2,26 @@ use crate::prelude::*; use delegate::delegate; use enum_iterator::reverse_all; -uniffi::custom_type!(ScryptoDecimal192, String); - -/// UniFFI conversion for InnerDecimal using String as builtin. -impl crate::UniffiCustomTypeConverter for ScryptoDecimal192 { - 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() - } -} +uniffi::custom_type!(ScryptoDecimal192, String, { + remote, + from_custom: |decimal| decimal.to_string(), + try_into_custom: |val| val.parse::().map_err(|e| e.into()) +}); + +// /// UniFFI conversion for InnerDecimal using String as builtin. +// impl crate::UniffiCustomTypeConverter for ScryptoDecimal192 { +// type Builtin = String; + +// #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests +// fn into_custom(val: Self::Builtin) -> uniffi::Result { +// +// } + +// #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests +// fn from_custom(obj: Self) -> Self::Builtin { +// obj.to_string() +// } +// } /// `Decimal192` represents a 192 bit representation of a fixed-scale decimal number. /// @@ -499,7 +503,7 @@ impl Decimal { impl Decimal { /// Creates the Decimal `10^exponent`, returns `None` if overflows. #[inline] - pub(crate) fn checked_powi(&self, exp: i64) -> Option { + pub fn checked_powi(&self, exp: i64) -> Option { self.native().checked_powi(exp).map(|n| n.into()) } @@ -658,7 +662,7 @@ impl Decimal { } } -#[cfg(test)] +// We want `#[cfg(test)]` but that seems to not work across crates :/ impl From<&str> for Decimal192 { /// TEST ONLY fn from(value: &str) -> Self { @@ -728,7 +732,7 @@ impl Decimal192 { uniffi::Enum, )] #[repr(u8)] -pub(crate) enum Multiplier { +pub enum Multiplier { Million = 6, Billion = 9, Trillion = 12, @@ -741,12 +745,12 @@ impl Multiplier { } /// The exponent of a `Multiplier`, i.e. `6` for `Million`. - pub(crate) fn value(&self) -> Decimal192 { + pub fn value(&self) -> Decimal192 { Decimal192::pow(self.discriminant()) } /// Symbol of a `Multiplier`, i.e. 'M' for `Million`. - pub(crate) fn suffix(&self) -> char { + pub fn suffix(&self) -> char { match self { Self::Million => 'M', Self::Billion => 'B', @@ -796,7 +800,7 @@ fn split_str(s: impl AsRef, after: i8) -> (String, String) { // Helper for formatting impl Decimal192 { - pub(crate) fn multiplier(&self) -> Option { + pub fn multiplier(&self) -> Option { let abs = self.abs(); reverse_all::().find(|x| x.value() <= abs) } @@ -808,7 +812,7 @@ impl Decimal192 { /// Rounds `self`` to `n` places, counting both the integer and decimal parts, /// as well as any leading zeros. - pub(crate) fn rounded_to_total_places(&self, n: u8) -> Self { + pub fn rounded_to_total_places(&self, n: u8) -> Self { let total_places = n; let digits = self.digits(); // If we only have decimals, we will still count the 0 before the separator as an integer diff --git a/src/core/types/decimal192_uniffi_fn.rs b/crates/common/src/types/decimal192_uniffi_fn.rs similarity index 100% rename from src/core/types/decimal192_uniffi_fn.rs rename to crates/common/src/types/decimal192_uniffi_fn.rs diff --git a/src/core/types/entity_kind.rs b/crates/common/src/types/entity_kind.rs similarity index 100% rename from src/core/types/entity_kind.rs rename to crates/common/src/types/entity_kind.rs diff --git a/src/core/types/epoch.rs b/crates/common/src/types/epoch.rs similarity index 100% rename from src/core/types/epoch.rs rename to crates/common/src/types/epoch.rs diff --git a/src/core/types/exactly_n_bytes.rs b/crates/common/src/types/exactly_n_bytes.rs similarity index 89% rename from src/core/types/exactly_n_bytes.rs rename to crates/common/src/types/exactly_n_bytes.rs index 0112b0dec..414b5a4cc 100644 --- a/src/core/types/exactly_n_bytes.rs +++ b/crates/common/src/types/exactly_n_bytes.rs @@ -23,7 +23,7 @@ macro_rules! decl_exactly_n_bytes { Ord, PartialOrd, )] - struct []([u8; $byte_count]); + pub struct []([u8; $byte_count]); impl From<&[u8; $byte_count]> for BagOfBytes { fn from(value: &[u8; $byte_count]) -> BagOfBytes { @@ -63,21 +63,11 @@ macro_rules! decl_exactly_n_bytes { } } - uniffi::custom_type!([], BagOfBytes); - - impl crate::UniffiCustomTypeConverter for [] { - type Builtin = BagOfBytes; - - #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Self::try_from(val.as_ref()).map_err(|e| e.into()) - } - - #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests - fn from_custom(obj: Self) -> Self::Builtin { - BagOfBytes::from(obj.to_vec()) - } - } + uniffi::custom_type!([], BagOfBytes, { + remote, + from_custom: |secret_magic| BagOfBytes::from(secret_magic.to_vec()), + try_into_custom: |bytes| Self::try_from(bytes.as_ref()).map_err(|e| e.into()) + }); $( #[doc = $expr] @@ -100,11 +90,11 @@ macro_rules! decl_exactly_n_bytes { #[display("{}", self.to_hex())] #[debug("{}", self.to_hex())] pub struct [] { - secret_magic: [], + pub secret_magic: [], } // Make it JSON String convertible in Swift/Kotlin - json_string_convertible!([]); + json_data_convertible!([]); impl From<[]> for [] { fn from(value: []) -> Self { @@ -370,33 +360,33 @@ macro_rules! decl_exactly_n_bytes { assert_eq!(set.len(), n); } - #[test] - fn manual_perform_uniffi_conversion_successful() { - let bytes = generate_byte_array::<$byte_count>(); - let bag_of_bytes = BagOfBytes::from(&bytes); - let secret_magic = [](bytes); - - let ffi_side = - <[] as crate::UniffiCustomTypeConverter>::from_custom(secret_magic); - - assert_eq!(ffi_side, bag_of_bytes); - - let from_ffi_side = <[] as crate::UniffiCustomTypeConverter>::into_custom( - bag_of_bytes, - ) - .unwrap(); - assert_eq!(secret_magic, from_ffi_side); - } - - #[test] - fn manual_perform_uniffi_conversion_fail() { - assert!( - <[] as crate::UniffiCustomTypeConverter>::into_custom( - BagOfBytes::from(vec![0xde, 0xad]), - ) - .is_err() - ); - } + // #[test] + // fn manual_perform_uniffi_conversion_successful() { + // let bytes = generate_byte_array::<$byte_count>(); + // let bag_of_bytes = BagOfBytes::from(&bytes); + // let secret_magic = [](bytes); + + // let ffi_side = + // <[] as crate::UniffiCustomTypeConverter>::from_custom(secret_magic); + + // assert_eq!(ffi_side, bag_of_bytes); + + // let from_ffi_side = <[] as crate::UniffiCustomTypeConverter>::into_custom( + // bag_of_bytes, + // ) + // .unwrap(); + // assert_eq!(secret_magic, from_ffi_side); + // } + + // #[test] + // fn manual_perform_uniffi_conversion_fail() { + // assert!( + // <[] as crate::UniffiCustomTypeConverter>::into_custom( + // BagOfBytes::from(vec![0xde, 0xad]), + // ) + // .is_err() + // ); + // } #[test] fn from_string_roundtrip() { diff --git a/src/core/types/identified_vec_of/identifiable.rs b/crates/common/src/types/identifiable.rs similarity index 100% rename from src/core/types/identified_vec_of/identifiable.rs rename to crates/common/src/types/identifiable.rs diff --git a/src/core/types/keys/ed25519/mod.rs b/crates/common/src/types/keys/ed25519/mod.rs similarity index 100% rename from src/core/types/keys/ed25519/mod.rs rename to crates/common/src/types/keys/ed25519/mod.rs diff --git a/src/core/types/keys/ed25519/private_key.rs b/crates/common/src/types/keys/ed25519/private_key.rs similarity index 99% rename from src/core/types/keys/ed25519/private_key.rs rename to crates/common/src/types/keys/ed25519/private_key.rs index 0a10c97a9..2ce5aac45 100644 --- a/src/core/types/keys/ed25519/private_key.rs +++ b/crates/common/src/types/keys/ed25519/private_key.rs @@ -25,51 +25,51 @@ impl PartialEq for Ed25519PrivateKey { impl Eq for Ed25519PrivateKey {} -impl IsPrivateKey for Ed25519PrivateKey { - fn curve() -> SLIP10Curve { - SLIP10Curve::Curve25519 +impl Ed25519PrivateKey { + pub fn from_scrypto(scrypto: ScryptoEd25519PrivateKey) -> Self { + Self(scrypto) } - type Signature = Ed25519Signature; - - fn public_key(&self) -> Ed25519PublicKey { - self.0.public_key().try_into().expect( - "Public Key from EC scalar multiplication should always be valid.", - ) + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes().to_vec() } - fn sign(&self, msg_hash: &Hash) -> Self::Signature { - self.0.sign(msg_hash).into() + pub fn to_hex(&self) -> String { + hex_encode(self.to_bytes()) } - fn from_bytes(slice: &[u8]) -> Result { + pub fn from_bytes(slice: &[u8]) -> Result { ScryptoEd25519PrivateKey::from_bytes(slice) .map_err(|_| CommonError::InvalidEd25519PrivateKeyFromBytes { bad_value: slice.into(), }) .map(Self::from_scrypto) } -} -impl Ed25519PrivateKey { - pub fn from_scrypto(scrypto: ScryptoEd25519PrivateKey) -> Self { - Self(scrypto) + pub fn from_vec(bytes: Vec) -> Result { + Self::from_bytes(bytes.as_slice()) } - pub fn to_bytes(&self) -> Vec { - self.0.to_bytes().to_vec() + pub fn from_exactly32_bytes(bytes: Exactly32Bytes) -> Result { + Self::from_vec(bytes.to_vec()) } +} - pub fn to_hex(&self) -> String { - hex_encode(self.to_bytes()) +impl IsPrivateKey for Ed25519PrivateKey { + fn curve() -> SLIP10Curve { + SLIP10Curve::Curve25519 } - pub fn from_vec(bytes: Vec) -> Result { - Self::from_bytes(bytes.as_slice()) + type Signature = Ed25519Signature; + + fn public_key(&self) -> Ed25519PublicKey { + self.0.public_key().try_into().expect( + "Public Key from EC scalar multiplication should always be valid.", + ) } - pub fn from_exactly32_bytes(bytes: Exactly32Bytes) -> Result { - Self::from_vec(bytes.to_vec()) + fn sign(&self, msg_hash: &Hash) -> Self::Signature { + self.0.sign(msg_hash).into() } } diff --git a/src/core/types/keys/ed25519/public_key.rs b/crates/common/src/types/keys/ed25519/public_key.rs similarity index 94% rename from src/core/types/keys/ed25519/public_key.rs rename to crates/common/src/types/keys/ed25519/public_key.rs index df0b1201b..529886565 100644 --- a/src/core/types/keys/ed25519/public_key.rs +++ b/crates/common/src/types/keys/ed25519/public_key.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, UniffiCustomTypeConverter}; +use crate::{json_string_convertible, prelude::*}; use crypto::signatures::ed25519 as IotaSlip10Ed25519; @@ -32,20 +32,11 @@ impl From for ScryptoEd25519PublicKey { } } -uniffi::custom_type!(ScryptoEd25519PublicKey, BagOfBytes); -impl UniffiCustomTypeConverter for ScryptoEd25519PublicKey { - type Builtin = BagOfBytes; - - #[cfg(not(tarpaulin_include))] // false negative | tested in bindgen tests - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Self::try_from(val.as_slice()).map_err(|e| e.into()) - } - - #[cfg(not(tarpaulin_include))] // false negative | tested in bindgen tests - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_vec().into() - } -} +uniffi::custom_type!(ScryptoEd25519PublicKey, BagOfBytes, { + remote, + from_custom: |key| BagOfBytes::from(key.to_vec()), + try_into_custom: |b| Ok(ScryptoEd25519PublicKey::try_from(b.as_slice()).unwrap()) +}); #[uniffi::export] pub fn new_ed25519_public_key_from_hex( @@ -109,7 +100,7 @@ impl IsPublicKey for Ed25519PublicKey { } impl Ed25519PublicKey { - pub(crate) fn scrypto(&self) -> ScryptoEd25519PublicKey { + pub fn scrypto(&self) -> ScryptoEd25519PublicKey { self.secret_magic } diff --git a/crates/common/src/types/keys/is_private_key.rs b/crates/common/src/types/keys/is_private_key.rs new file mode 100644 index 000000000..4e94aadda --- /dev/null +++ b/crates/common/src/types/keys/is_private_key.rs @@ -0,0 +1,11 @@ +use crate::prelude::*; + +pub trait IsPrivateKey>: Sized { + type Signature; + + fn curve() -> SLIP10Curve; + + fn public_key(&self) -> P; + + fn sign(&self, msg_hash: &Hash) -> Self::Signature; +} diff --git a/src/core/types/keys/is_public_key.rs b/crates/common/src/types/keys/is_public_key.rs similarity index 100% rename from src/core/types/keys/is_public_key.rs rename to crates/common/src/types/keys/is_public_key.rs diff --git a/src/core/types/keys/mod.rs b/crates/common/src/types/keys/mod.rs similarity index 100% rename from src/core/types/keys/mod.rs rename to crates/common/src/types/keys/mod.rs diff --git a/src/core/types/keys/private_key.rs b/crates/common/src/types/keys/private_key.rs similarity index 85% rename from src/core/types/keys/private_key.rs rename to crates/common/src/types/keys/private_key.rs index 2a099412e..a13824227 100644 --- a/src/core/types/keys/private_key.rs +++ b/crates/common/src/types/keys/private_key.rs @@ -58,30 +58,6 @@ impl PrivateKey { } } - pub fn sign_intent_hash( - &self, - intent_hash: &IntentHash, - ) -> IntentSignature { - match self { - PrivateKey::Ed25519(key) => SignatureWithPublicKey::Ed25519 { - public_key: key.public_key(), - signature: key.sign(&intent_hash.hash), - }, - PrivateKey::Secp256k1(key) => SignatureWithPublicKey::Secp256k1 { - public_key: key.public_key(), - signature: key.sign(&intent_hash.hash), - }, - } - .into() - } - - pub fn notarize_hash( - &self, - signed_intent_hash: &SignedIntentHash, - ) -> NotarySignature { - self.sign(&signed_intent_hash.hash).into() - } - /// Returns the hex representation of the inner private key's bytes as a `Vec`. pub fn to_bytes(&self) -> Vec { match self { @@ -102,7 +78,7 @@ impl PrivateKey { #[cfg(test)] mod tests { - use crate::prelude::*; + use super::*; #[test] fn private_key_ed25519_into_as_roundtrip() { diff --git a/src/core/types/keys/public_key.rs b/crates/common/src/types/keys/public_key.rs similarity index 100% rename from src/core/types/keys/public_key.rs rename to crates/common/src/types/keys/public_key.rs diff --git a/src/core/types/keys/public_key_uniffi_fn.rs b/crates/common/src/types/keys/public_key_uniffi_fn.rs similarity index 100% rename from src/core/types/keys/public_key_uniffi_fn.rs rename to crates/common/src/types/keys/public_key_uniffi_fn.rs diff --git a/src/core/types/keys/secp256k1/mod.rs b/crates/common/src/types/keys/secp256k1/mod.rs similarity index 100% rename from src/core/types/keys/secp256k1/mod.rs rename to crates/common/src/types/keys/secp256k1/mod.rs diff --git a/src/core/types/keys/secp256k1/private_key.rs b/crates/common/src/types/keys/secp256k1/private_key.rs similarity index 99% rename from src/core/types/keys/secp256k1/private_key.rs rename to crates/common/src/types/keys/secp256k1/private_key.rs index dd562a77f..8a2ddcff8 100644 --- a/src/core/types/keys/secp256k1/private_key.rs +++ b/crates/common/src/types/keys/secp256k1/private_key.rs @@ -44,6 +44,14 @@ impl Secp256k1PrivateKey { hex_encode(self.to_bytes()) } + pub fn from_bytes(slice: &[u8]) -> Result { + ScryptoSecp256k1PrivateKey::from_bytes(slice) + .map_err(|_| CommonError::InvalidSecp256k1PrivateKeyFromBytes { + bad_value: slice.to_owned().into(), + }) + .map(Self::from_scrypto) + } + pub fn from_vec(bytes: Vec) -> Result { Self::from_bytes(bytes.as_slice()) } @@ -53,26 +61,6 @@ impl Secp256k1PrivateKey { } } -impl FromStr for Secp256k1PrivateKey { - type Err = CommonError; - - fn from_str(s: &str) -> Result { - Exactly32Bytes::from_hex(s) - .map_err(|_| CommonError::InvalidSecp256k1PrivateKeyFromString { - bad_value: s.to_owned(), - }) - .and_then(Self::from_exactly32_bytes) - } -} - -impl TryFrom<&[u8]> for Secp256k1PrivateKey { - type Error = crate::CommonError; - - fn try_from(slice: &[u8]) -> Result { - Secp256k1PrivateKey::from_bytes(slice) - } -} - impl IsPrivateKey for Secp256k1PrivateKey { fn curve() -> SLIP10Curve { SLIP10Curve::Secp256k1 @@ -89,13 +77,25 @@ impl IsPrivateKey for Secp256k1PrivateKey { fn sign(&self, msg_hash: &Hash) -> Self::Signature { self.0.sign(msg_hash).into() } +} - fn from_bytes(slice: &[u8]) -> Result { - ScryptoSecp256k1PrivateKey::from_bytes(slice) - .map_err(|_| CommonError::InvalidSecp256k1PrivateKeyFromBytes { - bad_value: slice.to_owned().into(), +impl FromStr for Secp256k1PrivateKey { + type Err = CommonError; + + fn from_str(s: &str) -> Result { + Exactly32Bytes::from_hex(s) + .map_err(|_| CommonError::InvalidSecp256k1PrivateKeyFromString { + bad_value: s.to_owned(), }) - .map(Self::from_scrypto) + .and_then(Self::from_exactly32_bytes) + } +} + +impl TryFrom<&[u8]> for Secp256k1PrivateKey { + type Error = crate::CommonError; + + fn try_from(slice: &[u8]) -> Result { + Secp256k1PrivateKey::from_bytes(slice) } } diff --git a/src/core/types/keys/secp256k1/public_key.rs b/crates/common/src/types/keys/secp256k1/public_key.rs similarity index 95% rename from src/core/types/keys/secp256k1/public_key.rs rename to crates/common/src/types/keys/secp256k1/public_key.rs index 18b6f993d..d886c0907 100644 --- a/src/core/types/keys/secp256k1/public_key.rs +++ b/crates/common/src/types/keys/secp256k1/public_key.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, UniffiCustomTypeConverter}; +use crate::prelude::*; use k256::ecdsa::VerifyingKey as K256PublicKey; @@ -30,21 +30,11 @@ impl From for ScryptoSecp256k1PublicKey { } } -uniffi::custom_type!(ScryptoSecp256k1PublicKey, BagOfBytes); - -impl UniffiCustomTypeConverter for ScryptoSecp256k1PublicKey { - type Builtin = BagOfBytes; - - #[cfg(not(tarpaulin_include))] // false negative | tested in bindgen tests - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Self::try_from(val.as_slice()).map_err(|e| e.into()) - } - - #[cfg(not(tarpaulin_include))] // false negative | tested in bindgen tests - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_vec().into() - } -} +uniffi::custom_type!(ScryptoSecp256k1PublicKey, BagOfBytes, { + remote, + from_custom: |key| BagOfBytes::from(key.to_vec()), + try_into_custom: |b| ScryptoSecp256k1PublicKey::try_from(b.as_slice()).map_err(|e| e.into()) +}); impl IsPublicKey for Secp256k1PublicKey { /// Verifies an ECDSA signature over Secp256k1. @@ -62,7 +52,7 @@ impl IsPublicKey for Secp256k1PublicKey { } impl Secp256k1PublicKey { - pub(crate) fn scrypto(&self) -> ScryptoSecp256k1PublicKey { + pub fn scrypto(&self) -> ScryptoSecp256k1PublicKey { self.secret_magic } diff --git a/src/core/types/keys/secp256k1/public_key_uniffi_fn.rs b/crates/common/src/types/keys/secp256k1/public_key_uniffi_fn.rs similarity index 100% rename from src/core/types/keys/secp256k1/public_key_uniffi_fn.rs rename to crates/common/src/types/keys/secp256k1/public_key_uniffi_fn.rs diff --git a/src/core/types/keys/slip10_curve.rs b/crates/common/src/types/keys/slip10_curve.rs similarity index 100% rename from src/core/types/keys/slip10_curve.rs rename to crates/common/src/types/keys/slip10_curve.rs diff --git a/src/core/types/keys/slip10_curve_uniffi_fn.rs b/crates/common/src/types/keys/slip10_curve_uniffi_fn.rs similarity index 92% rename from src/core/types/keys/slip10_curve_uniffi_fn.rs rename to crates/common/src/types/keys/slip10_curve_uniffi_fn.rs index 63bce8c50..452609523 100644 --- a/src/core/types/keys/slip10_curve_uniffi_fn.rs +++ b/crates/common/src/types/keys/slip10_curve_uniffi_fn.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::{json_string_convertible, prelude::*}; json_string_convertible!(SLIP10Curve, "super invalid json string"); diff --git a/src/core/types/locale_config.rs b/crates/common/src/types/locale_config.rs similarity index 100% rename from src/core/types/locale_config.rs rename to crates/common/src/types/locale_config.rs diff --git a/src/core/types/logged_result.rs b/crates/common/src/types/logged_result.rs similarity index 100% rename from src/core/types/logged_result.rs rename to crates/common/src/types/logged_result.rs diff --git a/src/core/types/mod.rs b/crates/common/src/types/mod.rs similarity index 74% rename from src/core/types/mod.rs rename to crates/common/src/types/mod.rs index ed2ad0775..81eed9e17 100644 --- a/src/core/types/mod.rs +++ b/crates/common/src/types/mod.rs @@ -1,3 +1,4 @@ +mod abstract_entity_type; mod appearance_id; mod appearance_id_uniffi_fn; mod bag_of_bytes; @@ -7,10 +8,12 @@ mod decimal192_uniffi_fn; mod entity_kind; mod epoch; mod exactly_n_bytes; -mod identified_vec_of; +mod identifiable; mod keys; mod locale_config; mod logged_result; +mod network_id; +mod network_id_uniffi_fn; mod non_empty_max_n_bytes; mod nonce; mod nonce_uniffi_fn; @@ -22,6 +25,7 @@ mod safe_to_log; mod secret_bytes; mod signatures; +pub use abstract_entity_type::*; pub use appearance_id::*; pub use appearance_id_uniffi_fn::*; pub use bag_of_bytes::*; @@ -31,10 +35,12 @@ pub use decimal192_uniffi_fn::*; pub use entity_kind::*; pub use epoch::*; pub use exactly_n_bytes::*; -pub use identified_vec_of::*; +pub use identifiable::*; pub use keys::*; pub use locale_config::*; pub use logged_result::*; +pub use network_id::*; +pub use network_id_uniffi_fn::*; pub use non_empty_max_n_bytes::*; pub use nonce::*; pub use nonce_uniffi_fn::*; @@ -45,3 +51,9 @@ pub use rounding_mode::*; pub use safe_to_log::*; pub use secret_bytes::*; pub use signatures::*; + +impl From for aes_gcm::Key { + fn from(value: Exactly32Bytes) -> Self { + Self::from(*value.bytes()) + } +} diff --git a/src/profile/v100/networks/network/network_id.rs b/crates/common/src/types/network_id.rs similarity index 99% rename from src/profile/v100/networks/network/network_id.rs rename to crates/common/src/types/network_id.rs index bfc073ffe..b15380f06 100644 --- a/src/profile/v100/networks/network/network_id.rs +++ b/crates/common/src/types/network_id.rs @@ -128,7 +128,7 @@ impl NetworkID { /// Looks up a `ScryptoNetworkDefinition` in lookup table, /// this is used internally for radix_common::address::AddressBech32Decoder, /// and to read out the canonical name (logical name) for a network. - pub(crate) fn network_definition(&self) -> ScryptoNetworkDefinition { + pub fn network_definition(&self) -> ScryptoNetworkDefinition { use NetworkID::*; match self { Mainnet => ScryptoNetworkDefinition::mainnet(), diff --git a/src/profile/v100/networks/network/network_id_uniffi_fn.rs b/crates/common/src/types/network_id_uniffi_fn.rs similarity index 100% rename from src/profile/v100/networks/network/network_id_uniffi_fn.rs rename to crates/common/src/types/network_id_uniffi_fn.rs diff --git a/src/core/types/non_empty_max_n_bytes.rs b/crates/common/src/types/non_empty_max_n_bytes.rs similarity index 90% rename from src/core/types/non_empty_max_n_bytes.rs rename to crates/common/src/types/non_empty_max_n_bytes.rs index 886068c26..6e68ae40c 100644 --- a/src/core/types/non_empty_max_n_bytes.rs +++ b/crates/common/src/types/non_empty_max_n_bytes.rs @@ -151,6 +151,37 @@ decl_non_empty_max_n_bytes!( 32 ); +impl From<[u8; 32]> for NonEmptyMax32Bytes { + fn from(value: [u8; 32]) -> Self { + Self::try_from(value.as_slice()) + .expect("32 bytes is less than or equal to 32 bytes") + } +} +impl From<[u8; 28]> for NonEmptyMax32Bytes { + fn from(value: [u8; 28]) -> Self { + Self::try_from(value.as_slice()) + .expect("28 bytes is less than or equal to 32 bytes") + } +} +impl From<[u8; 24]> for NonEmptyMax32Bytes { + fn from(value: [u8; 24]) -> Self { + Self::try_from(value.as_slice()) + .expect("24 bytes is less than or equal to 32 bytes") + } +} +impl From<[u8; 20]> for NonEmptyMax32Bytes { + fn from(value: [u8; 20]) -> Self { + Self::try_from(value.as_slice()) + .expect("20 bytes is less than or equal to 32 bytes") + } +} +impl From<[u8; 16]> for NonEmptyMax32Bytes { + fn from(value: [u8; 16]) -> Self { + Self::try_from(value.as_slice()) + .expect("16 bytes is less than or equal to 32 bytes") + } +} + /// This macro exists since UniFFI does not support generics currently, when/if /// UniFFI does, we SHOULD remove this macro and use generics. macro_rules! decl_samples_for_max_n_bytes { @@ -217,6 +248,12 @@ macro_rules! decl_samples_for_max_n_bytes { // The impl of sample values require an max number of bytes decl_samples_for_max_n_bytes!(NonEmptyMax64Bytes, 64); +impl From for NonEmptyMax64Bytes { + fn from(value: ScryptoBytesNonFungibleLocalId) -> Self { + Self::try_from(value.value()).expect("Should not be possible, since ScryptoBytesNonFungibleLocalId have validated length") + } +} + #[cfg(test)] mod tests_non_empty_max_64_bytes { diff --git a/src/core/types/nonce.rs b/crates/common/src/types/nonce.rs similarity index 87% rename from src/core/types/nonce.rs rename to crates/common/src/types/nonce.rs index 51975d16f..a4d7e5c24 100644 --- a/src/core/types/nonce.rs +++ b/crates/common/src/types/nonce.rs @@ -138,14 +138,14 @@ mod tests { assert_eq!(set.len(), n); // with a low probability this might fail yes. } - #[test] - fn manual_perform_uniffi_conversion() { - let sut = SUT::sample(); - let ffi_side = - ::from_custom( - sut.secret_magic, - ); - let from_ffi_side = ::into_custom(ffi_side).unwrap(); - assert_eq!(sut, from_ffi_side.into()); - } + // #[test] + // fn manual_perform_uniffi_conversion() { + // let sut = SUT::sample(); + // let ffi_side = + // ::from_custom( + // sut.secret_magic, + // ); + // let from_ffi_side = ::into_custom(ffi_side).unwrap(); + // assert_eq!(sut, from_ffi_side.into()); + // } } diff --git a/src/core/types/nonce_uniffi_fn.rs b/crates/common/src/types/nonce_uniffi_fn.rs similarity index 100% rename from src/core/types/nonce_uniffi_fn.rs rename to crates/common/src/types/nonce_uniffi_fn.rs diff --git a/src/core/types/requested_number_quantifier.rs b/crates/common/src/types/requested_number_quantifier.rs similarity index 100% rename from src/core/types/requested_number_quantifier.rs rename to crates/common/src/types/requested_number_quantifier.rs diff --git a/src/core/types/requested_quantity.rs b/crates/common/src/types/requested_quantity.rs similarity index 100% rename from src/core/types/requested_quantity.rs rename to crates/common/src/types/requested_quantity.rs diff --git a/src/core/types/requested_quantity_uniffi_fn.rs b/crates/common/src/types/requested_quantity_uniffi_fn.rs similarity index 100% rename from src/core/types/requested_quantity_uniffi_fn.rs rename to crates/common/src/types/requested_quantity_uniffi_fn.rs diff --git a/src/core/types/rounding_mode.rs b/crates/common/src/types/rounding_mode.rs similarity index 100% rename from src/core/types/rounding_mode.rs rename to crates/common/src/types/rounding_mode.rs diff --git a/src/core/types/safe_to_log.rs b/crates/common/src/types/safe_to_log.rs similarity index 100% rename from src/core/types/safe_to_log.rs rename to crates/common/src/types/safe_to_log.rs diff --git a/crates/common/src/types/secret_bytes.rs b/crates/common/src/types/secret_bytes.rs new file mode 100644 index 000000000..77ac356b7 --- /dev/null +++ b/crates/common/src/types/secret_bytes.rs @@ -0,0 +1,181 @@ +// use crate::prelude::*; + +// #[macro_export] +// macro_rules! decl_secret_bytes { +// ( +// $( +// #[doc = $expr: expr] +// )* +// $struct_name: ident, +// $byte_count: literal +// ) => { +// paste! { + +// #[derive(Zeroize, ZeroizeOnDrop, derive_more::Debug, derive_more::Display)] +// #[debug("OBFUSCATED")] +// #[display("OBFUSCATED")] +// pub struct [< $struct_name SecretMagic >](Box<[u8; $byte_count]>); + +// uniffi::custom_type!([< $struct_name SecretMagic >], BagOfBytes); + +// impl TryFrom for [< $struct_name SecretMagic >] { +// type Error = CommonError; +// fn try_from(value: BagOfBytes) -> Result { +// let fixed_size: &[u8; $byte_count] = value.as_ref().try_into().map_err(|_| CommonError::InvalidByteCount { expected: $byte_count as u64, found: value.len() as u64 })?; +// Ok(Self(Box::new(*fixed_size))) +// } +// } + +// impl $crate::UniffiCustomTypeConverter for [< $struct_name SecretMagic >] { +// type Builtin = BagOfBytes; + +// fn into_custom(val: Self::Builtin) -> uniffi::Result { +// Self::try_from(val).map_err(|e| e.into()) +// } + +// fn from_custom(obj: Self) -> Self::Builtin { +// BagOfBytes::from(obj.0.as_slice()) +// } +// } + +// $( +// #[doc = $expr] +// )* +// #[derive(Zeroize, derive_more::Debug, derive_more::Display, uniffi::Record)] +// #[debug("OBFUSCATED")] +// #[display("OBFUSCATED")] +// pub struct $struct_name { +// secret_magic: [< $struct_name SecretMagic >] +// } + +// #[uniffi::export] +// pub fn [< new_ $struct_name:snake _from_bytes >](bytes: BagOfBytes) -> Result<$struct_name> { +// [< $struct_name SecretMagic >]::try_from(bytes) +// .map(|secret_magic| $struct_name { secret_magic }) +// } + +// impl $struct_name { +// pub fn to_bytes(&self) -> &[u8] { +// &self.secret_magic.0.as_slice() +// } +// } + +// impl HasSampleValues for $struct_name { +// fn sample() -> Self { +// Self { secret_magic: [< $struct_name SecretMagic >](Box::new([0xab; $byte_count])) } +// } + +// fn sample_other() -> Self { +// Self { secret_magic: [< $struct_name SecretMagic >](Box::new([0xde; $byte_count])) } +// } +// } + +// #[uniffi::export] +// pub fn [< new_ $struct_name:snake _sample >]() -> $struct_name { +// $struct_name::sample() +// } + +// #[uniffi::export] +// pub fn [< new_ $struct_name:snake _sample_other >]() -> $struct_name { +// $struct_name::sample_other() +// } + +// #[uniffi::export] +// pub fn [< $struct_name:snake _to_bytes >](bytes: &$struct_name) -> BagOfBytes { +// BagOfBytes::from(bytes.to_bytes()) +// } + +// impl $struct_name { +// pub const LENGTH: usize = $byte_count; + +// pub fn new(bytes: [u8; Self::LENGTH]) -> Self { +// Self { +// secret_magic: [< $struct_name SecretMagic >](Box::new(bytes)) +// } +// } + +// #[allow(unused)] +// pub fn is_zeroized(&self) -> bool { +// *self.secret_magic.0 == [0; Self::LENGTH] +// } +// } + +// #[cfg(test)] +// mod [< uniffi_ $struct_name:snake tests >] { +// use super::*; + +// #[allow(clippy::upper_case_acronyms)] +// type SUT = $struct_name; + +// #[test] +// fn test_from_bytes() { +// let too_few_bytes = BagOfBytes::from_str("dead").unwrap(); +// assert!([< new_ $struct_name:snake _from_bytes >](too_few_bytes).is_err()); +// } + +// #[test] +// fn get_bytes() { +// let sut = SUT::sample(); +// let bytes = [< $struct_name:snake _to_bytes >](&sut); +// assert_eq!(bytes.as_ref(), [0xab; $byte_count]); +// } + +// #[test] +// fn zeroize_sample() { +// let mut sut = [< new_ $struct_name:snake _sample >](); +// assert!(!sut.is_zeroized()); +// sut.zeroize(); +// assert!(sut.is_zeroized()); +// } + +// #[test] +// fn zeroize_sample_other() { +// let mut sut = [< new_ $struct_name:snake _sample_other >](); +// assert!(!sut.is_zeroized()); +// sut.zeroize(); +// assert!(sut.is_zeroized()); +// } + +// #[test] +// fn test_to_bytes() { +// let sut = [< new_ $struct_name:snake _sample >](); +// assert_eq!( +// sut.secret_magic.0.as_slice(), +// sut.to_bytes() +// ) +// } +// } + +// #[cfg(test)] +// mod [< $struct_name:snake tests >] { +// use super::*; + +// #[allow(clippy::upper_case_acronyms)] +// type SUT = $struct_name; + +// #[test] +// fn zeroize() { +// let mut sut = SUT::sample(); +// assert!(!sut.is_zeroized()); +// sut.zeroize(); +// assert!(sut.is_zeroized()); +// } + +// #[test] +// fn debug_obfuscates_secret() { +// let sut = SUT::sample_other(); +// assert_eq!(format!("{:?}", sut), "OBFUSCATED"); +// } + +// #[test] +// fn display_obfuscates_secret() { +// let sut = SUT::sample_other(); +// assert_eq!(format!("{}", sut), "OBFUSCATED"); +// } +// } + +// } +// }; +// } + +// pub use decl_secret_bytes; diff --git a/src/core/types/signatures/ed25519_signature.rs b/crates/common/src/types/signatures/ed25519_signature.rs similarity index 99% rename from src/core/types/signatures/ed25519_signature.rs rename to crates/common/src/types/signatures/ed25519_signature.rs index 175df4b38..622a57c8e 100644 --- a/src/core/types/signatures/ed25519_signature.rs +++ b/crates/common/src/types/signatures/ed25519_signature.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::{json_string_convertible, prelude::*}; json_string_convertible!(Ed25519Signature); diff --git a/src/core/types/signatures/ed25519_signature_uniffi_fn.rs b/crates/common/src/types/signatures/ed25519_signature_uniffi_fn.rs similarity index 100% rename from src/core/types/signatures/ed25519_signature_uniffi_fn.rs rename to crates/common/src/types/signatures/ed25519_signature_uniffi_fn.rs diff --git a/src/core/types/signatures/mod.rs b/crates/common/src/types/signatures/mod.rs similarity index 100% rename from src/core/types/signatures/mod.rs rename to crates/common/src/types/signatures/mod.rs diff --git a/src/core/types/signatures/secp256k1_signature.rs b/crates/common/src/types/signatures/secp256k1_signature.rs similarity index 100% rename from src/core/types/signatures/secp256k1_signature.rs rename to crates/common/src/types/signatures/secp256k1_signature.rs diff --git a/src/core/types/signatures/secp256k1_signature_uniffi_fn.rs b/crates/common/src/types/signatures/secp256k1_signature_uniffi_fn.rs similarity index 100% rename from src/core/types/signatures/secp256k1_signature_uniffi_fn.rs rename to crates/common/src/types/signatures/secp256k1_signature_uniffi_fn.rs diff --git a/src/core/types/signatures/signature.rs b/crates/common/src/types/signatures/signature.rs similarity index 100% rename from src/core/types/signatures/signature.rs rename to crates/common/src/types/signatures/signature.rs diff --git a/src/core/types/signatures/signature_uniffi_fn.rs b/crates/common/src/types/signatures/signature_uniffi_fn.rs similarity index 100% rename from src/core/types/signatures/signature_uniffi_fn.rs rename to crates/common/src/types/signatures/signature_uniffi_fn.rs diff --git a/src/core/types/signatures/signature_with_public_key.rs b/crates/common/src/types/signatures/signature_with_public_key.rs similarity index 100% rename from src/core/types/signatures/signature_with_public_key.rs rename to crates/common/src/types/signatures/signature_with_public_key.rs diff --git a/src/core/types/signatures/signature_with_public_key_uniffi_fn.rs b/crates/common/src/types/signatures/signature_with_public_key_uniffi_fn.rs similarity index 87% rename from src/core/types/signatures/signature_with_public_key_uniffi_fn.rs rename to crates/common/src/types/signatures/signature_with_public_key_uniffi_fn.rs index 1a36e2666..8e3b50eed 100644 --- a/src/core/types/signatures/signature_with_public_key_uniffi_fn.rs +++ b/crates/common/src/types/signatures/signature_with_public_key_uniffi_fn.rs @@ -69,12 +69,4 @@ mod tests { SUT::sample().signature() ) } - - #[test] - fn is_valid() { - let private_key = HierarchicalDeterministicPrivateKey::sample(); - let msg = Hash::sample(); - let sut = private_key.sign(&msg); - assert!(signature_with_public_key_is_valid(&sut, &msg)); - } } diff --git a/src/core/unsafe_id_stepper.rs b/crates/common/src/unsafe_id_stepper.rs similarity index 100% rename from src/core/unsafe_id_stepper.rs rename to crates/common/src/unsafe_id_stepper.rs diff --git a/crates/common/src/utils/factory.rs b/crates/common/src/utils/factory.rs new file mode 100644 index 000000000..124865418 --- /dev/null +++ b/crates/common/src/utils/factory.rs @@ -0,0 +1,62 @@ +use crate::prelude::*; + +/// A JSON "stable" timestamp, that is to say, this has already been JSON +/// roundtripped, ensuring the same value will be decoded once encoded, this is +/// a bit hacky solution to the fact that `07:18:08.284647000Z` when encoded +/// and then decoded become `07:18:08.284000000Z` resulting in problems for +/// equality checks. +pub fn now() -> Timestamp { + let t = Timestamp::now_utc(); + let json = serde_json::to_vec(&t).unwrap(); + serde_json::from_slice(json.as_slice()).unwrap() +} + +pub fn id() -> Uuid { + Uuid::new_v4() +} + +pub fn iso8601(dt: &Timestamp) -> String { + let (h, m, s) = dt.as_hms(); + format!("{} {:02}:{:02}:{:02}", date(dt), h, m, s) +} + +pub fn date(dt: &Timestamp) -> String { + dt.date().to_string() +} + +impl HasSampleValues for Uuid { + fn sample() -> Self { + Self::from_bytes([0xff; 16]) + } + + fn sample_other() -> Self { + Self::from_bytes([0xde; 16]) + } +} +impl HasSampleValues for Timestamp { + fn sample() -> Self { + Self::parse("2023-09-11T16:05:56Z").unwrap() + } + + fn sample_other() -> Self { + Self::parse("2023-12-24T17:13:56.123Z").unwrap() + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn id_unique() { + let n = 100; + let set = (0..n).map(|_| id()).collect::>(); + assert_eq!(set.len(), n); + } + + #[test] + fn date_str() { + assert_eq!(date(&Timestamp::UNIX_EPOCH), "1970-01-01"); + assert_eq!(iso8601(&Timestamp::UNIX_EPOCH), "1970-01-01 00:00:00"); + } +} diff --git a/src/profile/v100/json_data_convertible.rs b/crates/common/src/utils/json_data_convertible.rs similarity index 95% rename from src/profile/v100/json_data_convertible.rs rename to crates/common/src/utils/json_data_convertible.rs index 6d64ec0ff..c92be99a0 100644 --- a/src/profile/v100/json_data_convertible.rs +++ b/crates/common/src/utils/json_data_convertible.rs @@ -51,12 +51,13 @@ pub trait JsonDataSerializing: Sized + Serialize { } } +#[macro_export] macro_rules! json_data_convertible { ($type: ty) => { - paste! { - - impl JsonDataDeserializing for $type {} - impl JsonDataSerializing for $type {} + paste::paste! { + use $crate::prelude::*; + impl $crate::prelude::JsonDataDeserializing for $type {} + impl $crate::prelude::JsonDataSerializing for $type {} #[uniffi::export] pub fn [< new_ $type:snake _from_json_bytes >]( @@ -117,8 +118,9 @@ macro_rules! json_data_convertible { }; } -pub(crate) use json_data_convertible; +pub use json_data_convertible; +#[macro_export] macro_rules! json_string_convertible { ($type: ty) => { paste! { @@ -198,4 +200,4 @@ macro_rules! json_string_convertible { }; } -pub(crate) use json_string_convertible; +pub use json_string_convertible; diff --git a/src/core/utils/logged_panic.rs b/crates/common/src/utils/logged_panic.rs similarity index 100% rename from src/core/utils/logged_panic.rs rename to crates/common/src/utils/logged_panic.rs diff --git a/src/core/utils/mod.rs b/crates/common/src/utils/mod.rs similarity index 87% rename from src/core/utils/mod.rs rename to crates/common/src/utils/mod.rs index c0be3e89d..cde0e1279 100644 --- a/src/core/utils/mod.rs +++ b/crates/common/src/utils/mod.rs @@ -1,8 +1,10 @@ mod factory; +mod json_data_convertible; mod logged_panic; mod string_utils; pub use factory::*; +pub use json_data_convertible::*; pub use logged_panic::*; pub use string_utils::*; diff --git a/src/core/utils/string_utils.rs b/crates/common/src/utils/string_utils.rs similarity index 100% rename from src/core/utils/string_utils.rs rename to crates/common/src/utils/string_utils.rs diff --git a/uniffi.toml b/crates/common/uniffi.toml similarity index 94% rename from uniffi.toml rename to crates/common/uniffi.toml index 785268a79..451483e8b 100644 --- a/uniffi.toml +++ b/crates/common/uniffi.toml @@ -1,9 +1,11 @@ +namespace = "sargoncommon" + [bindings.swift] -module_name = "Sargon" +module_name = "SargonCommon" experimental_sendable_value_types = true [bindings.kotlin] -package_name = "com.radixdlt.sargon" +package_name = "com.radixdlt.sargon.common" [bindings.swift.custom_types.BagOfBytes] type_name = "Data" diff --git a/crates/drivers/Cargo.toml b/crates/drivers/Cargo.toml new file mode 100644 index 000000000..af108d3de --- /dev/null +++ b/crates/drivers/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "drivers" +version = "1.1.0" +edition = "2021" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } +serde = { workspace = true } +strum = { workspace = true } +log = { workspace = true } +reqwest = { workspace = true } +serde_json = { workspace = true } +pretty_env_logger = { workspace = true } +pretty_assertions = { workspace = true } +async-trait = { workspace = true } +enum-iterator = { workspace = true } +actix-rt = { workspace = true } +profile = { path = "../profile" } +sargoncommon = { path = "../common" } +ret = { path = "../ret" } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } + +[lib] +crate-type = ["staticlib", "cdylib", "lib"] diff --git a/crates/drivers/build.rs b/crates/drivers/build.rs new file mode 100644 index 000000000..57015226a --- /dev/null +++ b/crates/drivers/build.rs @@ -0,0 +1,4 @@ +pub fn main() { + uniffi::generate_scaffolding("src/drivers.udl") + .expect("Should be able to build."); +} diff --git a/crates/drivers/src/drivers.udl b/crates/drivers/src/drivers.udl new file mode 100644 index 000000000..2dea6f58b --- /dev/null +++ b/crates/drivers/src/drivers.udl @@ -0,0 +1 @@ +namespace drivers {}; diff --git a/crates/drivers/src/drivers/README.md b/crates/drivers/src/drivers/README.md new file mode 100644 index 000000000..cbb6a51f3 --- /dev/null +++ b/crates/drivers/src/drivers/README.md @@ -0,0 +1,5 @@ +# Driver + +"Drivers" are traits implemented by the FFI host with methods we call from Rust. + +Driver are created during initialization of the BIOS and are passed to appropriate "subsystems" during POST of the BIOS. When the BIOS is used to boot the (Sargon)OS, the OS creates "clients" and keeps them around for the duration of the OS lifetime. diff --git a/crates/drivers/src/drivers/drivers.rs b/crates/drivers/src/drivers/drivers.rs new file mode 100644 index 000000000..9e9669eb8 --- /dev/null +++ b/crates/drivers/src/drivers/drivers.rs @@ -0,0 +1,260 @@ +use crate::prelude::*; + +#[derive(Debug, uniffi::Object)] +pub struct Drivers { + pub networking: Arc, + pub secure_storage: Arc, + pub entropy_provider: Arc, + pub host_info: Arc, + pub logging: Arc, + pub event_bus: Arc, + pub file_system: Arc, + pub unsafe_storage: Arc, +} + +#[uniffi::export] +impl Drivers { + pub fn networking(&self) -> Arc { + self.networking.clone() + } + pub fn secure_storage(&self) -> Arc { + self.secure_storage.clone() + } + pub fn entropy_provider(&self) -> Arc { + self.entropy_provider.clone() + } + pub fn host_info(&self) -> Arc { + self.host_info.clone() + } + pub fn logging(&self) -> Arc { + self.logging.clone() + } + pub fn event_bus(&self) -> Arc { + self.event_bus.clone() + } + pub fn file_system(&self) -> Arc { + self.file_system.clone() + } + pub fn unsafe_storage(&self) -> Arc { + self.unsafe_storage.clone() + } +} + +#[uniffi::export] +impl Drivers { + #[uniffi::constructor] + #[allow(clippy::too_many_arguments)] + pub fn new( + networking: Arc, + secure_storage: Arc, + entropy_provider: Arc, + host_info: Arc, + logging: Arc, + event_bus: Arc, + file_system: Arc, + unsafe_storage: Arc, + ) -> Arc { + Arc::new(Self { + networking, + secure_storage, + entropy_provider, + host_info, + logging, + event_bus, + file_system, + unsafe_storage, + }) + } +} + +// #[cfg(test)] // FIXME: multi-crate test +impl Drivers { + pub fn test() -> Arc { + Drivers::new( + RustNetworkingDriver::new(), + EphemeralSecureStorage::new(), + RustEntropyDriver::new(), + RustHostInfoDriver::new(), + RustLoggingDriver::new(), + RustEventBusDriver::new(), + RustFileSystemDriver::new(), + EphemeralUnsafeStorage::new(), + ) + } + + pub fn with_networking(networking: Arc) -> Arc { + Drivers::new( + networking, + EphemeralSecureStorage::new(), + RustEntropyDriver::new(), + RustHostInfoDriver::new(), + RustLoggingDriver::new(), + RustEventBusDriver::new(), + RustFileSystemDriver::new(), + EphemeralUnsafeStorage::new(), + ) + } + + pub fn with_secure_storage( + secure_storage: Arc, + ) -> Arc { + Drivers::new( + RustNetworkingDriver::new(), + secure_storage, + RustEntropyDriver::new(), + RustHostInfoDriver::new(), + RustLoggingDriver::new(), + RustEventBusDriver::new(), + RustFileSystemDriver::new(), + EphemeralUnsafeStorage::new(), + ) + } + + pub fn with_entropy_provider( + entropy_provider: Arc, + ) -> Arc { + Drivers::new( + RustNetworkingDriver::new(), + EphemeralSecureStorage::new(), + entropy_provider, + RustHostInfoDriver::new(), + RustLoggingDriver::new(), + RustEventBusDriver::new(), + RustFileSystemDriver::new(), + EphemeralUnsafeStorage::new(), + ) + } + + pub fn with_host_info(host_info: Arc) -> Arc { + Drivers::new( + RustNetworkingDriver::new(), + EphemeralSecureStorage::new(), + RustEntropyDriver::new(), + host_info, + RustLoggingDriver::new(), + RustEventBusDriver::new(), + RustFileSystemDriver::new(), + EphemeralUnsafeStorage::new(), + ) + } + + pub fn with_logging(logging: Arc) -> Arc { + Drivers::new( + RustNetworkingDriver::new(), + EphemeralSecureStorage::new(), + RustEntropyDriver::new(), + RustHostInfoDriver::new(), + logging, + RustEventBusDriver::new(), + RustFileSystemDriver::new(), + EphemeralUnsafeStorage::new(), + ) + } + + pub fn with_event_bus(event_bus: Arc) -> Arc { + Drivers::new( + RustNetworkingDriver::new(), + EphemeralSecureStorage::new(), + RustEntropyDriver::new(), + RustHostInfoDriver::new(), + RustLoggingDriver::new(), + event_bus, + RustFileSystemDriver::new(), + EphemeralUnsafeStorage::new(), + ) + } + + pub fn with_file_system( + file_system: Arc, + ) -> Arc { + Drivers::new( + RustNetworkingDriver::new(), + EphemeralSecureStorage::new(), + RustEntropyDriver::new(), + RustHostInfoDriver::new(), + RustLoggingDriver::new(), + RustEventBusDriver::new(), + file_system, + EphemeralUnsafeStorage::new(), + ) + } + + pub fn with_unsafe_storage( + unsafe_storage: Arc, + ) -> Arc { + Drivers::new( + RustNetworkingDriver::new(), + EphemeralSecureStorage::new(), + RustEntropyDriver::new(), + RustHostInfoDriver::new(), + RustLoggingDriver::new(), + RustEventBusDriver::new(), + RustFileSystemDriver::new(), + unsafe_storage, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Drivers; + + #[test] + fn get_networking() { + let d = RustNetworkingDriver::new(); + let sut = SUT::with_networking(d.clone()); + assert_eq!(Arc::as_ptr(&sut.networking()), Arc::as_ptr(&d)); + } + + #[test] + fn get_secure_storage() { + let d = EphemeralSecureStorage::new(); + let sut = SUT::with_secure_storage(d.clone()); + assert_eq!(Arc::as_ptr(&sut.secure_storage()), Arc::as_ptr(&d)); + } + + #[test] + fn get_entropy_provider() { + let d = RustEntropyDriver::new(); + let sut = SUT::with_entropy_provider(d.clone()); + assert_eq!(Arc::as_ptr(&sut.entropy_provider()), Arc::as_ptr(&d)); + } + + #[test] + fn get_host_info() { + let d = RustHostInfoDriver::new(); + let sut = SUT::with_host_info(d.clone()); + assert_eq!(Arc::as_ptr(&sut.host_info()), Arc::as_ptr(&d)); + } + + #[test] + fn get_logging() { + let d = RustLoggingDriver::new(); + let sut = SUT::with_logging(d.clone()); + assert_eq!(Arc::as_ptr(&sut.logging()), Arc::as_ptr(&d)); + } + + #[test] + fn get_event_bus() { + let d = RustEventBusDriver::new(); + let sut = SUT::with_event_bus(d.clone()); + assert_eq!(Arc::as_ptr(&sut.event_bus()), Arc::as_ptr(&d)); + } + + #[test] + fn get_file_system() { + let d = RustFileSystemDriver::new(); + let sut = SUT::with_file_system(d.clone()); + assert_eq!(Arc::as_ptr(&sut.file_system()), Arc::as_ptr(&d)); + } + + #[test] + fn get_unsafe_storage() { + let d = EphemeralUnsafeStorage::new(); + let sut = SUT::with_unsafe_storage(d.clone()); + assert_eq!(Arc::as_ptr(&sut.unsafe_storage()), Arc::as_ptr(&d)); + } +} diff --git a/crates/drivers/src/drivers/entropy_provider_driver/entropy_provider_driver.rs b/crates/drivers/src/drivers/entropy_provider_driver/entropy_provider_driver.rs new file mode 100644 index 000000000..9ce3f25b2 --- /dev/null +++ b/crates/drivers/src/drivers/entropy_provider_driver/entropy_provider_driver.rs @@ -0,0 +1,6 @@ +use crate::prelude::*; + +#[uniffi::export(with_foreign)] +pub trait EntropyProviderDriver: Send + Sync + std::fmt::Debug { + fn generate_secure_random_bytes(&self) -> Exactly32Bytes; +} diff --git a/crates/drivers/src/drivers/entropy_provider_driver/mod.rs b/crates/drivers/src/drivers/entropy_provider_driver/mod.rs new file mode 100644 index 000000000..e8799bd67 --- /dev/null +++ b/crates/drivers/src/drivers/entropy_provider_driver/mod.rs @@ -0,0 +1,5 @@ +mod entropy_provider_driver; +mod support; + +pub use entropy_provider_driver::*; +pub use support::*; diff --git a/crates/drivers/src/drivers/entropy_provider_driver/support/mod.rs b/crates/drivers/src/drivers/entropy_provider_driver/support/mod.rs new file mode 100644 index 000000000..9abea5028 --- /dev/null +++ b/crates/drivers/src/drivers/entropy_provider_driver/support/mod.rs @@ -0,0 +1,3 @@ +mod test; + +pub use test::*; diff --git a/crates/drivers/src/drivers/entropy_provider_driver/support/test/mod.rs b/crates/drivers/src/drivers/entropy_provider_driver/support/test/mod.rs new file mode 100644 index 000000000..0644e0179 --- /dev/null +++ b/crates/drivers/src/drivers/entropy_provider_driver/support/test/mod.rs @@ -0,0 +1,3 @@ +mod rust_entropy_driver; + +pub use rust_entropy_driver::*; diff --git a/crates/drivers/src/drivers/entropy_provider_driver/support/test/rust_entropy_driver.rs b/crates/drivers/src/drivers/entropy_provider_driver/support/test/rust_entropy_driver.rs new file mode 100644 index 000000000..2f0b809c9 --- /dev/null +++ b/crates/drivers/src/drivers/entropy_provider_driver/support/test/rust_entropy_driver.rs @@ -0,0 +1,17 @@ +use crate::prelude::*; + +/// A **Rust** entropy driver using `osrnd`. +#[derive(Debug)] +pub struct RustEntropyDriver; + +impl RustEntropyDriver { + pub fn new() -> Arc { + Arc::new(RustEntropyDriver) + } +} + +impl EntropyProviderDriver for RustEntropyDriver { + fn generate_secure_random_bytes(&self) -> Exactly32Bytes { + Exactly32Bytes::generate() + } +} diff --git a/crates/drivers/src/drivers/event_bus_driver/event_bus_driver.rs b/crates/drivers/src/drivers/event_bus_driver/event_bus_driver.rs new file mode 100644 index 000000000..2966bbe76 --- /dev/null +++ b/crates/drivers/src/drivers/event_bus_driver/event_bus_driver.rs @@ -0,0 +1,26 @@ +use crate::prelude::*; + +/// A driver which received and asynchronously *handles* event notifications +/// emitted by the `SargonOS`. Letting the method be async allows for Rust side +/// to wait for host clients to complete something which might require user +/// attention. E.g. presentation of an alert and await user input. +/// +/// Due to limitations in UniFFI and lack of first class citizen support of +/// async sequences (like we have in Swift) we cannot export an accessor of the +/// received events here. Instead implementing types on the FFI side SHOULD +/// create the driver as a singleton object they can reference later and build +/// async streams in that implementing type. +/// +/// See Swifts EventBus implementation for more details. +#[uniffi::export(with_foreign)] +#[async_trait::async_trait] +pub trait EventBusDriver: Send + Sync + std::fmt::Debug { + /// Asynchronously *handles* event notifications + /// emitted by the `SargonOS`. Letting the method be async allows for Rust side + /// to wait for host clients to complete something which might require user + /// attention. E.g. presentation of an alert and await user input. + async fn handle_event_notification( + &self, + event_notification: EventNotification, + ); +} diff --git a/crates/drivers/src/drivers/event_bus_driver/mod.rs b/crates/drivers/src/drivers/event_bus_driver/mod.rs new file mode 100644 index 000000000..e3865cd27 --- /dev/null +++ b/crates/drivers/src/drivers/event_bus_driver/mod.rs @@ -0,0 +1,5 @@ +mod event_bus_driver; +mod support; + +pub use event_bus_driver::*; +pub use support::*; diff --git a/crates/drivers/src/drivers/event_bus_driver/support/event.rs b/crates/drivers/src/drivers/event_bus_driver/support/event.rs new file mode 100644 index 000000000..5e96c4453 --- /dev/null +++ b/crates/drivers/src/drivers/event_bus_driver/support/event.rs @@ -0,0 +1,122 @@ +use crate::prelude::*; + +/// SargonOS event contain information about something of interest that has +/// happened to the SargonOS, most prominently to the Profile, host device +/// can subscribe to these events by use of `EventBusDriver`. +#[derive(Debug, Clone, PartialEq, Eq, Hash, uniffi::Enum)] +pub enum Event { + /// The SargonOS just booted. + Booted, + + /// Current Gateway changed + GatewayChangedCurrent { to: Gateway, is_new: bool }, + + /// Profile has been saved, typically it has been modified and the new + /// changed Profile got persisted into secure storage. + ProfileSaved, + + /// A profile has been imported and has been set to active profile, + /// and saved into secure storage. + ImportedProfile { id: ProfileID }, + + /// The active profile has been modified (might not have been saved yet). + ModifiedProfile { change: EventProfileModified }, + + /// The Profile was last used on another device, user ought to claim it. + ProfileLastUsedOnOtherDevice(DeviceInfo), +} + +impl Event { + pub fn profile_modified(change: EventProfileModified) -> Self { + Self::ModifiedProfile { change } + } + + pub fn profile_last_used_on_other_device(device: DeviceInfo) -> Self { + Self::ProfileLastUsedOnOtherDevice(device) + } +} + +impl HasEventKind for Event { + fn kind(&self) -> EventKind { + match self { + Self::Booted => EventKind::Booted, + Self::GatewayChangedCurrent { to: _, is_new: _ } => { + EventKind::GatewayChangedCurrent + } + Self::ModifiedProfile { change } => change.kind(), + Self::ProfileLastUsedOnOtherDevice(_) => { + EventKind::ProfileLastUsedOnOtherDevice + } + Self::ImportedProfile { id: _ } => EventKind::ImportedProfile, + Self::ProfileSaved => EventKind::ProfileSaved, + } + } +} + +impl HasSampleValues for Event { + fn sample() -> Self { + Self::Booted + } + + fn sample_other() -> Self { + Self::ModifiedProfile { + change: EventProfileModified::sample_other(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Event; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn last_used_on_other_device() { + let device = DeviceInfo::sample(); + let sut = SUT::profile_last_used_on_other_device(device.clone()); + assert_eq!(sut, SUT::ProfileLastUsedOnOtherDevice(device)) + } + + #[test] + fn test_kind() { + let test = |s: SUT, exp: EventKind| { + assert_eq!(s.kind(), exp); + }; + test( + SUT::ImportedProfile { + id: ProfileID::sample(), + }, + EventKind::ImportedProfile, + ); + test(SUT::ProfileSaved, EventKind::ProfileSaved); + test( + SUT::GatewayChangedCurrent { + to: Gateway::sample(), + is_new: false, + }, + EventKind::GatewayChangedCurrent, + ); + let change = EventProfileModified::AddedAccount { + address: AccountAddress::sample(), + }; + test( + SUT::ModifiedProfile { + change: change.clone(), + }, + change.kind(), + ); + } +} diff --git a/crates/drivers/src/drivers/event_bus_driver/support/event_kind.rs b/crates/drivers/src/drivers/event_bus_driver/support/event_kind.rs new file mode 100644 index 000000000..4a0808c42 --- /dev/null +++ b/crates/drivers/src/drivers/event_bus_driver/support/event_kind.rs @@ -0,0 +1,61 @@ +use crate::prelude::*; + +/// A discriminator identifying the kind of `Event`, this has no associated +/// values and flattens the otherwise nested `Event` enum. +#[derive(Debug, Clone, PartialEq, Eq, Hash, uniffi::Enum)] +pub enum EventKind { + /* Sort lexicographically */ + /// Profile updated with a new account. + AddedAccount, + + /// Profile updated with new accounts. + AddedAccounts, + + /// SargonOS did boot. + Booted, + + /// Current Gateway changed + GatewayChangedCurrent, + + /// Profile was saved. + ProfileSaved, + + /// A profile has been imported and has been set to active profile, + /// and saved into secure storage. + ImportedProfile, + + /// Profile was last used on another device. + ProfileLastUsedOnOtherDevice, + + /// An existing account has been updated + UpdatedAccount, +} + +impl HasSampleValues for EventKind { + fn sample() -> Self { + Self::Booted + } + + fn sample_other() -> Self { + Self::ProfileSaved + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = EventKind; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/crates/drivers/src/drivers/event_bus_driver/support/event_notification.rs b/crates/drivers/src/drivers/event_bus_driver/support/event_notification.rs new file mode 100644 index 000000000..7b618f644 --- /dev/null +++ b/crates/drivers/src/drivers/event_bus_driver/support/event_notification.rs @@ -0,0 +1,65 @@ +use crate::prelude::*; + +/// A notification containing a timestamped and unique `event`, host client +/// can subscribe to these notifications by using the EventBusDriver. +#[derive(Debug, Clone, PartialEq, Eq, Hash, uniffi::Record)] +pub struct EventNotification { + pub id: Uuid, + pub event: Event, + pub timestamp: Timestamp, +} + +impl EventNotification { + pub fn new(event: Event) -> Self { + Self { + id: id(), + event, + timestamp: now(), + } + } + + pub fn profile_changed(change: EventProfileModified) -> Self { + Self::new(Event::profile_modified(change)) + } + + pub fn profile_used_on_other_device(other_device: DeviceInfo) -> Self { + Self::new(Event::profile_last_used_on_other_device(other_device)) + } +} + +impl HasSampleValues for EventNotification { + fn sample() -> Self { + Self { + id: Uuid::sample(), + event: Event::sample(), + timestamp: Timestamp::sample(), + } + } + + fn sample_other() -> Self { + Self { + id: Uuid::sample_other(), + event: Event::sample_other(), + timestamp: Timestamp::sample_other(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = EventNotification; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/crates/drivers/src/drivers/event_bus_driver/support/event_profile_modified.rs b/crates/drivers/src/drivers/event_bus_driver/support/event_profile_modified.rs new file mode 100644 index 000000000..827643ab3 --- /dev/null +++ b/crates/drivers/src/drivers/event_bus_driver/support/event_profile_modified.rs @@ -0,0 +1,85 @@ +use crate::prelude::*; + +/// The active profile has been modified (might not have been saved yet). +#[derive(Debug, Clone, PartialEq, Eq, Hash, uniffi::Enum)] +pub enum EventProfileModified { + /// A new account with `address` was inserted into the active profile + AddedAccount { address: AccountAddress }, + + /// New accounts with `addresses` were inserted into the active profile + AddedAccounts { addresses: Vec }, + + /// An existing account has been updated + UpdatedAccount { address: AccountAddress }, +} + +impl HasEventKind for EventProfileModified { + fn kind(&self) -> EventKind { + match self { + Self::UpdatedAccount { address: _ } => EventKind::UpdatedAccount, + Self::AddedAccount { address: _ } => EventKind::AddedAccount, + Self::AddedAccounts { addresses: _ } => EventKind::AddedAccounts, + } + } +} + +impl HasSampleValues for EventProfileModified { + fn sample() -> Self { + Self::AddedAccount { + address: AccountAddress::sample(), + } + } + + fn sample_other() -> Self { + Self::AddedAccounts { + addresses: vec![ + AccountAddress::sample_mainnet_other(), + AccountAddress::sample_mainnet(), + ], + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = EventProfileModified; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn test_kind() { + let test = |s: SUT, exp: EventKind| { + assert_eq!(s.kind(), exp); + }; + test( + SUT::AddedAccount { + address: AccountAddress::sample(), + }, + EventKind::AddedAccount, + ); + test( + SUT::UpdatedAccount { + address: AccountAddress::sample(), + }, + EventKind::UpdatedAccount, + ); + test( + SUT::AddedAccounts { + addresses: vec![AccountAddress::sample()], + }, + EventKind::AddedAccounts, + ); + } +} diff --git a/crates/drivers/src/drivers/event_bus_driver/support/event_uniffi_fn.rs b/crates/drivers/src/drivers/event_bus_driver/support/event_uniffi_fn.rs new file mode 100644 index 000000000..be8988628 --- /dev/null +++ b/crates/drivers/src/drivers/event_bus_driver/support/event_uniffi_fn.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; + +#[uniffi::export] +pub fn event_kind(event: &Event) -> EventKind { + event.kind() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Event; + + #[test] + fn test_kind() { + assert_eq!(event_kind(&SUT::Booted), EventKind::Booted); + } +} diff --git a/crates/drivers/src/drivers/event_bus_driver/support/has_event_kind.rs b/crates/drivers/src/drivers/event_bus_driver/support/has_event_kind.rs new file mode 100644 index 000000000..4644366f7 --- /dev/null +++ b/crates/drivers/src/drivers/event_bus_driver/support/has_event_kind.rs @@ -0,0 +1,7 @@ +use crate::prelude::*; + +/// Event which can be turned into a discriminant `EventKind`, +/// which does not have any associated values. +pub trait HasEventKind { + fn kind(&self) -> EventKind; +} diff --git a/crates/drivers/src/drivers/event_bus_driver/support/mod.rs b/crates/drivers/src/drivers/event_bus_driver/support/mod.rs new file mode 100644 index 000000000..22d3a96ac --- /dev/null +++ b/crates/drivers/src/drivers/event_bus_driver/support/mod.rs @@ -0,0 +1,15 @@ +mod event; +mod event_kind; +mod event_notification; +mod event_profile_modified; +mod event_uniffi_fn; +mod has_event_kind; +mod test; + +pub use event::*; +pub use event_kind::*; +pub use event_notification::*; +pub use event_profile_modified::*; +pub use event_uniffi_fn::*; +pub use has_event_kind::*; +pub use test::*; diff --git a/crates/drivers/src/drivers/event_bus_driver/support/test/mod.rs b/crates/drivers/src/drivers/event_bus_driver/support/test/mod.rs new file mode 100644 index 000000000..c77d65e6a --- /dev/null +++ b/crates/drivers/src/drivers/event_bus_driver/support/test/mod.rs @@ -0,0 +1,3 @@ +mod rust_event_bus_driver; + +pub use rust_event_bus_driver::*; diff --git a/crates/drivers/src/drivers/event_bus_driver/support/test/rust_event_bus_driver.rs b/crates/drivers/src/drivers/event_bus_driver/support/test/rust_event_bus_driver.rs new file mode 100644 index 000000000..cc61f9795 --- /dev/null +++ b/crates/drivers/src/drivers/event_bus_driver/support/test/rust_event_bus_driver.rs @@ -0,0 +1,37 @@ +use crate::prelude::*; +use std::sync::RwLock; + +#[derive(Debug)] +pub struct RustEventBusDriver { + recorded: RwLock>, + spy: fn(EventNotification) -> (), +} + +#[async_trait::async_trait] +impl EventBusDriver for RustEventBusDriver { + async fn handle_event_notification( + &self, + event_notification: EventNotification, + ) { + self.recorded + .try_write() + .unwrap() + .push(event_notification.clone()); + (self.spy)(event_notification) + } +} + +impl RustEventBusDriver { + pub fn recorded(&self) -> Vec { + self.recorded.try_read().unwrap().clone() + } + pub fn new() -> Arc { + Self::with_spy(|_| {}) + } + pub fn with_spy(spy: fn(EventNotification) -> ()) -> Arc { + Arc::new(Self { + spy, + recorded: RwLock::new(Vec::new()), + }) + } +} diff --git a/crates/drivers/src/drivers/file_system_driver/file_system_driver.rs b/crates/drivers/src/drivers/file_system_driver/file_system_driver.rs new file mode 100644 index 000000000..74c849fda --- /dev/null +++ b/crates/drivers/src/drivers/file_system_driver/file_system_driver.rs @@ -0,0 +1,11 @@ +use crate::prelude::*; + +#[uniffi::export(with_foreign)] +#[async_trait::async_trait] +pub trait FileSystemDriver: Send + Sync + std::fmt::Debug { + async fn load_from_file(&self, path: String) -> Result>; + + async fn save_to_file(&self, path: String, data: BagOfBytes) -> Result<()>; + + async fn delete_file(&self, path: String) -> Result<()>; +} diff --git a/crates/drivers/src/drivers/file_system_driver/mod.rs b/crates/drivers/src/drivers/file_system_driver/mod.rs new file mode 100644 index 000000000..e722fda5c --- /dev/null +++ b/crates/drivers/src/drivers/file_system_driver/mod.rs @@ -0,0 +1,5 @@ +mod file_system_driver; +mod support; + +pub use file_system_driver::*; +pub use support::*; diff --git a/crates/drivers/src/drivers/file_system_driver/support/mod.rs b/crates/drivers/src/drivers/file_system_driver/support/mod.rs new file mode 100644 index 000000000..9abea5028 --- /dev/null +++ b/crates/drivers/src/drivers/file_system_driver/support/mod.rs @@ -0,0 +1,3 @@ +mod test; + +pub use test::*; diff --git a/crates/drivers/src/drivers/file_system_driver/support/test/mod.rs b/crates/drivers/src/drivers/file_system_driver/support/test/mod.rs new file mode 100644 index 000000000..31fa20b3b --- /dev/null +++ b/crates/drivers/src/drivers/file_system_driver/support/test/mod.rs @@ -0,0 +1,3 @@ +mod rust_file_system_driver; + +pub use rust_file_system_driver::*; diff --git a/crates/drivers/src/drivers/file_system_driver/support/test/rust_file_system_driver.rs b/crates/drivers/src/drivers/file_system_driver/support/test/rust_file_system_driver.rs new file mode 100644 index 000000000..2b8b597e7 --- /dev/null +++ b/crates/drivers/src/drivers/file_system_driver/support/test/rust_file_system_driver.rs @@ -0,0 +1,137 @@ +use std::path::PathBuf; + +use crate::prelude::*; + +#[derive(Debug)] +pub struct RustFileSystemDriver; + +impl RustFileSystemDriver { + pub fn new() -> Arc { + Arc::new(RustFileSystemDriver) + } +} + +#[allow(dead_code)] +pub fn path_from_str(str: String, require: bool) -> Result { + let path = PathBuf::from_str(&str).map_err(|_| CommonError::Unknown)?; + if require { + path.try_exists().map_err(|_| CommonError::Unknown)?; + Ok(path) + } else { + Ok(path) + } +} + +#[async_trait::async_trait] +impl FileSystemDriver for RustFileSystemDriver { + async fn load_from_file(&self, path: String) -> Result> { + let path = path_from_str(path, true)?; + match fs::read(path) { + Err(e) => match e.kind() { + std::io::ErrorKind::NotFound => Ok(None), + _ => Err(CommonError::Unknown), + }, + Ok(contents) => Ok(Some(BagOfBytes::from(contents))), + } + } + + async fn save_to_file(&self, path: String, data: BagOfBytes) -> Result<()> { + let path = path_from_str(path, false)?; + fs::write(path, data.as_ref()).map_err(|_| CommonError::Unknown)?; + Ok(()) + } + + async fn delete_file(&self, path: String) -> Result<()> { + let path = path_from_str(path, false)?; + match fs::remove_file(path) { + Ok(_) => Ok(()), + Err(e) => match e.kind() { + std::io::ErrorKind::NotFound => Ok(()), + _ => Err(CommonError::Unknown), + }, + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use std::path::Path; + + #[allow(clippy::upper_case_acronyms)] + type SUT = RustFileSystemDriver; + + fn file_in_dir(dir_path: impl AsRef) -> String { + let dir_path = dir_path.as_ref(); + assert!(std::fs::create_dir_all(dir_path).is_ok()); + + let file_name = format!("delete-me--generated-by-test-{}.txt", id()); + let file_path_buf = dir_path.join(file_name); + let file = String::from(file_path_buf.to_string_lossy()); + file + } + + fn file_in_tmp() -> String { + let dir_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("target/tmp"); + file_in_dir(dir_path) + } + + fn contents() -> BagOfBytes { + BagOfBytes::from("this file is completely safe to delete. it was generated by RustFileSystemDriver test.".as_bytes()) + } + + #[actix_rt::test] + async fn test_create_load_delete() { + let sut = SUT::new(); + let file = file_in_tmp(); + + let data = contents(); + sut.save_to_file(file.clone(), data.clone()).await.unwrap(); + let loaded = sut.load_from_file(file.clone()).await.unwrap().unwrap(); + assert_eq!(loaded, data); + assert!(sut.delete_file(file.clone()).await.is_ok()); + } + + #[actix_rt::test] + async fn test_load_non_existing_is_ok() { + let sut = SUT::new(); + let res = sut.load_from_file("non-existing".to_owned()).await; + assert_eq!(res, Ok(None)); + } + + #[actix_rt::test] + async fn test_load_fail() { + let sut = SUT::new(); + let res = sut.load_from_file("/".to_owned()).await; + assert_eq!(res, Err(CommonError::Unknown)); + } + + #[actix_rt::test] + async fn test_delete_non_existing_is_ok() { + let sut = SUT::new(); + let res = sut.delete_file("does not exist".to_owned()).await; + assert_eq!(res, Ok(())); + } + + #[actix_rt::test] + async fn test_save_to_root_is_err() { + let sut = SUT::new(); + let file = file_in_dir(Path::new("/")); + let res = sut.save_to_file(file, contents()).await; + assert_eq!(res, Err(CommonError::Unknown)); + } + + #[actix_rt::test] + async fn test_delete_dir_does_not_work() { + let sut = SUT::new(); + let res = sut + .delete_file(String::from( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("target/") + .to_string_lossy(), + )) + .await; + assert_eq!(res, Err(CommonError::Unknown)); + } +} diff --git a/crates/drivers/src/drivers/host_info_driver/host_info_driver.rs b/crates/drivers/src/drivers/host_info_driver/host_info_driver.rs new file mode 100644 index 000000000..52f4ec4c3 --- /dev/null +++ b/crates/drivers/src/drivers/host_info_driver/host_info_driver.rs @@ -0,0 +1,29 @@ +use crate::prelude::*; + +#[uniffi::export(with_foreign)] +#[async_trait::async_trait] +pub trait HostInfoDriver: Send + Sync + std::fmt::Debug { + /// The name of the host device (iPhone/Android), e.g. "My Red iPhone" + async fn host_device_name(&self) -> String; + + /// The **current** version of the device's operating system, e.g. "iOS 17.4.1" + async fn host_device_system_version(&self) -> String; + + /// The **current** version of the host app, for example the Radix iOS Wallet version - e.g. "1.6.1". + async fn host_app_version(&self) -> String; + + /// The model of the host device (iPhone/Android), .e.g. "iPhone SE 2nd Gen" + async fn host_device_model(&self) -> String; + + /// A **stable** id that unique identified the host device (iPhone/Android), this + /// MUST be stable and SHOULD NOT be generated by the host device, if the host OS + /// does not support this (iOS does not), then return `None`. + /// + /// Android seems to support it if `READ_PRIVILEGED_PHONE_STATE` permission is + /// obtained https://source.android.com/docs/core/connect/device-identifiers + async fn host_device_id(&self) -> Option; + + /// The vendor of the host device, e.g. "Apple" for iPhone, or "Samsung", + /// for Android host clients. + async fn host_device_vendor(&self) -> String; +} diff --git a/crates/drivers/src/drivers/host_info_driver/mod.rs b/crates/drivers/src/drivers/host_info_driver/mod.rs new file mode 100644 index 000000000..f4e4e27b7 --- /dev/null +++ b/crates/drivers/src/drivers/host_info_driver/mod.rs @@ -0,0 +1,5 @@ +mod host_info_driver; +mod support; + +pub use host_info_driver::*; +pub use support::*; diff --git a/crates/drivers/src/drivers/host_info_driver/support/mod.rs b/crates/drivers/src/drivers/host_info_driver/support/mod.rs new file mode 100644 index 000000000..9abea5028 --- /dev/null +++ b/crates/drivers/src/drivers/host_info_driver/support/mod.rs @@ -0,0 +1,3 @@ +mod test; + +pub use test::*; diff --git a/crates/drivers/src/drivers/host_info_driver/support/test/mod.rs b/crates/drivers/src/drivers/host_info_driver/support/test/mod.rs new file mode 100644 index 000000000..a1f11acc1 --- /dev/null +++ b/crates/drivers/src/drivers/host_info_driver/support/test/mod.rs @@ -0,0 +1,3 @@ +mod rust_host_info_driver; + +pub use rust_host_info_driver::*; diff --git a/crates/drivers/src/drivers/host_info_driver/support/test/rust_host_info_driver.rs b/crates/drivers/src/drivers/host_info_driver/support/test/rust_host_info_driver.rs new file mode 100644 index 000000000..21083006d --- /dev/null +++ b/crates/drivers/src/drivers/host_info_driver/support/test/rust_host_info_driver.rs @@ -0,0 +1,43 @@ +use std::env; + +use crate::prelude::*; + +#[derive(Debug)] +pub struct RustHostInfoDriver; + +impl RustHostInfoDriver { + pub fn new() -> Arc { + Arc::new(RustHostInfoDriver) + } +} + +#[async_trait::async_trait] +impl HostInfoDriver for RustHostInfoDriver { + /// The name of the host device (iPhone/Android), e.g. "My Red iPhone" + async fn host_device_name(&self) -> String { + "Rosebud".to_owned() + } + + /// The **current** version of the device's operating system, e.g. "iOS 17.4.1" + async fn host_device_system_version(&self) -> String { + format!("{}", env::consts::OS) + } + + /// The **current** version of the host app, for example the Radix iOS Wallet version - e.g. "1.6.1". + async fn host_app_version(&self) -> String { + env!("CARGO_PKG_VERSION").to_owned() + } + + /// The model of the host device (iPhone/Android), .e.g. "iPhone SE 2nd Gen" + async fn host_device_model(&self) -> String { + "Rust Sargon Unknown Device Model".to_owned() + } + + async fn host_device_id(&self) -> Option { + None + } + + async fn host_device_vendor(&self) -> String { + "Rust Sargon Unknown Vendor".to_owned() + } +} diff --git a/crates/drivers/src/drivers/logging_driver/logging_driver.rs b/crates/drivers/src/drivers/logging_driver/logging_driver.rs new file mode 100644 index 000000000..5f64099f1 --- /dev/null +++ b/crates/drivers/src/drivers/logging_driver/logging_driver.rs @@ -0,0 +1,11 @@ +use crate::prelude::*; + +/// * Trace +/// * Debug +/// * Info +/// * Warning +/// * Error +#[uniffi::export(with_foreign)] +pub trait LoggingDriver: Send + Sync + std::fmt::Debug { + fn log(&self, level: LogLevel, msg: String); +} diff --git a/crates/drivers/src/drivers/logging_driver/mod.rs b/crates/drivers/src/drivers/logging_driver/mod.rs new file mode 100644 index 000000000..53aab3cad --- /dev/null +++ b/crates/drivers/src/drivers/logging_driver/mod.rs @@ -0,0 +1,5 @@ +mod logging_driver; +mod support; + +pub use logging_driver::*; +pub use support::*; diff --git a/crates/drivers/src/drivers/logging_driver/support/log_level.rs b/crates/drivers/src/drivers/logging_driver/support/log_level.rs new file mode 100644 index 000000000..26e487960 --- /dev/null +++ b/crates/drivers/src/drivers/logging_driver/support/log_level.rs @@ -0,0 +1,189 @@ +use crate::prelude::*; + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + enum_iterator::Sequence, + uniffi::Enum, +)] +#[repr(u8)] +pub enum LogLevel { + /// The "error" level. + /// + /// Designates very serious errors. + Error = 1, + + /// The "warn" level. + /// + /// Designates hazardous situations. + Warn, + + /// The "info" level. + /// + /// Designates useful information. + Info, + + /// The "debug" level. + /// + /// Designates lower priority information. + Debug, + + /// The "trace" level. + /// + /// Designates very low priority, often extremely verbose, information. + Trace, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + enum_iterator::Sequence, + uniffi::Enum, +)] +#[repr(u8)] +pub enum LogFilter { + /// Logging is turned off + Off = 0, + + /// The "error" level. + /// + /// Designates very serious errors. + Error, + + /// The "warn" level. + /// + /// Designates hazardous situations. + Warn, + + /// The "info" level. + /// + /// Designates useful information. + Info, + + /// The "debug" level. + /// + /// Designates lower priority information. + Debug, + + /// The "trace" level. + /// + /// Designates very low priority, often extremely verbose, information. + Trace, +} + +impl From for LogLevel { + fn from(value: log::Level) -> Self { + match value { + log::Level::Error => Self::Error, + log::Level::Warn => Self::Warn, + log::Level::Info => Self::Info, + log::Level::Debug => Self::Debug, + log::Level::Trace => Self::Trace, + } + } +} + +impl From for log::Level { + fn from(value: LogLevel) -> Self { + match value { + LogLevel::Error => Self::Error, + LogLevel::Warn => Self::Warn, + LogLevel::Info => Self::Info, + LogLevel::Debug => Self::Debug, + LogLevel::Trace => Self::Trace, + } + } +} + +impl From for log::LevelFilter { + fn from(value: LogFilter) -> Self { + match value { + LogFilter::Off => Self::Off, + LogFilter::Error => Self::Error, + LogFilter::Warn => Self::Warn, + LogFilter::Info => Self::Info, + LogFilter::Debug => Self::Debug, + LogFilter::Trace => Self::Trace, + } + } +} + +impl From for LogFilter { + fn from(value: log::LevelFilter) -> Self { + match value { + log::LevelFilter::Off => Self::Off, + log::LevelFilter::Error => Self::Error, + log::LevelFilter::Warn => Self::Warn, + log::LevelFilter::Info => Self::Info, + log::LevelFilter::Debug => Self::Debug, + log::LevelFilter::Trace => Self::Trace, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_filter_from_log() { + let test = |l: log::LevelFilter, exp: LogFilter| { + let x = LogFilter::from(l); + assert_eq!(x, exp); + }; + test(log::LevelFilter::Off, LogFilter::Off); + test(log::LevelFilter::Error, LogFilter::Error); + test(log::LevelFilter::Warn, LogFilter::Warn); + test(log::LevelFilter::Info, LogFilter::Info); + test(log::LevelFilter::Debug, LogFilter::Debug); + test(log::LevelFilter::Trace, LogFilter::Trace); + } + + #[test] + fn test_filter_to_log() { + let test = |l: LogFilter, exp: log::LevelFilter| { + let x = log::LevelFilter::from(l); + assert_eq!(x, exp); + }; + test(LogFilter::Off, log::LevelFilter::Off); + test(LogFilter::Error, log::LevelFilter::Error); + test(LogFilter::Warn, log::LevelFilter::Warn); + test(LogFilter::Info, log::LevelFilter::Info); + test(LogFilter::Debug, log::LevelFilter::Debug); + test(LogFilter::Trace, log::LevelFilter::Trace); + } + + #[test] + fn test_level_to_log() { + let test = |l: LogLevel, exp: log::Level| { + let x = log::Level::from(l); + assert_eq!(x, exp); + }; + test(LogLevel::Error, log::Level::Error); + test(LogLevel::Warn, log::Level::Warn); + test(LogLevel::Info, log::Level::Info); + test(LogLevel::Debug, log::Level::Debug); + test(LogLevel::Trace, log::Level::Trace); + } + + #[test] + fn test_level_from_log() { + let test = |l: log::Level, exp: LogLevel| { + let x = LogLevel::from(l); + assert_eq!(x, exp); + }; + test(log::Level::Error, LogLevel::Error); + test(log::Level::Warn, LogLevel::Warn); + test(log::Level::Info, LogLevel::Info); + test(log::Level::Debug, LogLevel::Debug); + test(log::Level::Trace, LogLevel::Trace); + } +} diff --git a/crates/drivers/src/drivers/logging_driver/support/mod.rs b/crates/drivers/src/drivers/logging_driver/support/mod.rs new file mode 100644 index 000000000..0694d6b87 --- /dev/null +++ b/crates/drivers/src/drivers/logging_driver/support/mod.rs @@ -0,0 +1,5 @@ +mod log_level; +mod test; + +pub use log_level::*; +pub use test::*; diff --git a/crates/drivers/src/drivers/logging_driver/support/test/mod.rs b/crates/drivers/src/drivers/logging_driver/support/test/mod.rs new file mode 100644 index 000000000..8d2514c42 --- /dev/null +++ b/crates/drivers/src/drivers/logging_driver/support/test/mod.rs @@ -0,0 +1,3 @@ +mod rust_logging_driver; + +pub use rust_logging_driver::*; diff --git a/crates/drivers/src/drivers/logging_driver/support/test/rust_logging_driver.rs b/crates/drivers/src/drivers/logging_driver/support/test/rust_logging_driver.rs new file mode 100644 index 000000000..9bb224d6e --- /dev/null +++ b/crates/drivers/src/drivers/logging_driver/support/test/rust_logging_driver.rs @@ -0,0 +1,29 @@ +use log::Log; + +use crate::prelude::*; + +#[derive(Debug)] +pub struct RustLoggingDriver { + logger: pretty_env_logger::env_logger::Logger, +} + +impl RustLoggingDriver { + pub fn new() -> Arc { + Arc::new(Self { + logger: pretty_env_logger::env_logger::builder() + .parse_default_env() + .build(), + }) + } +} + +impl LoggingDriver for RustLoggingDriver { + fn log(&self, level: LogLevel, msg: String) { + self.logger.log( + &log::Record::builder() + .level(level.into()) + .args(format_args!("{}", msg)) + .build(), + ); + } +} diff --git a/crates/drivers/src/drivers/mod.rs b/crates/drivers/src/drivers/mod.rs new file mode 100644 index 000000000..849788d03 --- /dev/null +++ b/crates/drivers/src/drivers/mod.rs @@ -0,0 +1,19 @@ +mod drivers; +mod entropy_provider_driver; +mod event_bus_driver; +mod file_system_driver; +mod host_info_driver; +mod logging_driver; +mod networking_driver; +mod secure_storage_driver; +mod unsafe_storage_driver; + +pub use drivers::*; +pub use entropy_provider_driver::*; +pub use event_bus_driver::*; +pub use file_system_driver::*; +pub use host_info_driver::*; +pub use logging_driver::*; +pub use networking_driver::*; +pub use secure_storage_driver::*; +pub use unsafe_storage_driver::*; diff --git a/crates/drivers/src/drivers/networking_driver/mod.rs b/crates/drivers/src/drivers/networking_driver/mod.rs new file mode 100644 index 000000000..706a34f1a --- /dev/null +++ b/crates/drivers/src/drivers/networking_driver/mod.rs @@ -0,0 +1,5 @@ +mod networking_driver; +mod support; + +pub use networking_driver::*; +pub use support::*; diff --git a/crates/drivers/src/drivers/networking_driver/networking_driver.rs b/crates/drivers/src/drivers/networking_driver/networking_driver.rs new file mode 100644 index 000000000..8203e3ef1 --- /dev/null +++ b/crates/drivers/src/drivers/networking_driver/networking_driver.rs @@ -0,0 +1,17 @@ +use crate::prelude::*; + +/// Trait for executing network requests, to be implemented on the FFI side +/// (iOS/Android), so that Sargon can with some HTTP client perform make network +/// requests. +#[uniffi::export(with_foreign)] +#[async_trait::async_trait] +pub trait NetworkingDriver: Send + Sync + std::fmt::Debug { + /// Method invoked by Sargon Rust side, telling an implementing type to + /// execute a `NetworkRequest` and pass the Result back to Sargon Rust side. + /// + /// Either: `Err` or `Ok(NetworkResponse)`. + async fn execute_network_request( + &self, + request: NetworkRequest, + ) -> Result; +} diff --git a/src/http_client/network_antenna/mod.rs b/crates/drivers/src/drivers/networking_driver/support/mod.rs similarity index 60% rename from src/http_client/network_antenna/mod.rs rename to crates/drivers/src/drivers/networking_driver/support/mod.rs index c7e3dfc08..e5879b18f 100644 --- a/src/http_client/network_antenna/mod.rs +++ b/crates/drivers/src/drivers/networking_driver/support/mod.rs @@ -1,9 +1,13 @@ -mod network_antenna; mod network_method; +mod network_method_uniffi_fn; mod network_request; mod network_response; -pub use network_antenna::*; +mod test; + pub use network_method::*; +pub use network_method_uniffi_fn::*; pub use network_request::*; pub use network_response::*; + +pub use test::*; diff --git a/src/http_client/network_antenna/network_method.rs b/crates/drivers/src/drivers/networking_driver/support/network_method.rs similarity index 52% rename from src/http_client/network_antenna/network_method.rs rename to crates/drivers/src/drivers/networking_driver/support/network_method.rs index e51eb4489..8dac1ca4b 100644 --- a/src/http_client/network_antenna/network_method.rs +++ b/crates/drivers/src/drivers/networking_driver/support/network_method.rs @@ -1,15 +1,28 @@ use crate::prelude::*; -#[derive(Clone, Copy, Debug, PartialEq, Eq, uniffi::Enum)] +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + strum::EnumString, + strum::Display, + uniffi::Enum, +)] +#[strum(serialize_all = "UPPERCASE")] pub enum NetworkMethod { Post, Get, + Head, } impl HasSampleValues for NetworkMethod { fn sample() -> Self { NetworkMethod::Post } + fn sample_other() -> Self { NetworkMethod::Get } @@ -32,4 +45,16 @@ mod tests { fn inequality() { assert_ne!(SUT::sample(), SUT::sample_other()); } + + #[test] + fn str_roundtrip() { + let test = |m: SUT, s: &str| { + assert_eq!(SUT::from_str(s).unwrap(), m); + assert_eq!(m.to_string(), s); + assert_eq!(SUT::from_str(&m.to_string()).unwrap(), m); + }; + test(SUT::Post, "POST"); + test(SUT::Get, "GET"); + test(SUT::Head, "HEAD"); + } } diff --git a/crates/drivers/src/drivers/networking_driver/support/network_method_uniffi_fn.rs b/crates/drivers/src/drivers/networking_driver/support/network_method_uniffi_fn.rs new file mode 100644 index 000000000..6c2f07e56 --- /dev/null +++ b/crates/drivers/src/drivers/networking_driver/support/network_method_uniffi_fn.rs @@ -0,0 +1,45 @@ +use crate::prelude::*; + +#[uniffi::export] +pub fn new_network_method_sample() -> NetworkMethod { + NetworkMethod::sample() +} + +#[uniffi::export] +pub fn new_network_method_sample_other() -> NetworkMethod { + NetworkMethod::sample_other() +} + +#[uniffi::export] +pub fn network_method_to_string(method: &NetworkMethod) -> String { + method.to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = NetworkMethod; + + #[test] + fn hash_of_samples() { + assert_eq!( + HashSet::::from_iter([ + new_network_method_sample(), + new_network_method_sample_other(), + // duplicates should get removed + new_network_method_sample(), + new_network_method_sample_other(), + ]) + .len(), + 2 + ); + } + + #[test] + fn test_network_method_to_string() { + let sut = SUT::Post; + assert_eq!(network_method_to_string(&sut), sut.to_string()); + } +} diff --git a/src/http_client/network_antenna/network_request.rs b/crates/drivers/src/drivers/networking_driver/support/network_request.rs similarity index 80% rename from src/http_client/network_antenna/network_request.rs rename to crates/drivers/src/drivers/networking_driver/support/network_request.rs index e45f85340..046b7fc0f 100644 --- a/src/http_client/network_antenna/network_request.rs +++ b/crates/drivers/src/drivers/networking_driver/support/network_request.rs @@ -9,6 +9,20 @@ pub struct NetworkRequest { pub body: BagOfBytes, } +impl NetworkRequest { + pub fn with_gateway_api_headers(self) -> Self { + let headers = HashMap::::from_iter([ + ("content-Type".to_owned(), "application/json".to_owned()), + ("accept".to_owned(), "application/json".to_owned()), + ("user-agent".to_owned(), "Sargon".to_owned()), // https://stackoverflow.com/a/77866494/1311272 + ("RDX-Client-Name".to_owned(), "Sargon".to_owned()), + ("RDX-Client-Version".to_owned(), "1.5.1".to_owned()), + ]); + + self.with_headers(headers) + } +} + impl NetworkRequest { pub fn new_post(url: Url) -> Self { Self { diff --git a/crates/drivers/src/drivers/networking_driver/support/network_request_uniffi_fn.rs b/crates/drivers/src/drivers/networking_driver/support/network_request_uniffi_fn.rs new file mode 100644 index 000000000..aff15ff83 --- /dev/null +++ b/crates/drivers/src/drivers/networking_driver/support/network_request_uniffi_fn.rs @@ -0,0 +1,34 @@ +use crate::prelude::*; + +#[uniffi::export] +pub fn new_network_request_sample() -> NetworkRequest { + NetworkRequest::sample() +} + +#[uniffi::export] +pub fn new_network_request_sample_other() -> NetworkRequest { + NetworkRequest::sample_other() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = NetworkRequest; + + #[test] + fn hash_of_samples() { + assert_eq!( + HashSet::::from_iter([ + new_network_request_sample(), + new_network_request_sample_other(), + // duplicates should get removed + new_network_request_sample(), + new_network_request_sample_other(), + ]) + .len(), + 2 + ); + } +} diff --git a/src/http_client/network_antenna/network_response.rs b/crates/drivers/src/drivers/networking_driver/support/network_response.rs similarity index 100% rename from src/http_client/network_antenna/network_response.rs rename to crates/drivers/src/drivers/networking_driver/support/network_response.rs diff --git a/src/http_client/network_antenna/network_antenna.rs b/crates/drivers/src/drivers/networking_driver/support/test/mock_networking_driver.rs similarity index 65% rename from src/http_client/network_antenna/network_antenna.rs rename to crates/drivers/src/drivers/networking_driver/support/test/mock_networking_driver.rs index 80ee1b50c..5374ab44d 100644 --- a/src/http_client/network_antenna/network_antenna.rs +++ b/crates/drivers/src/drivers/networking_driver/support/test/mock_networking_driver.rs @@ -1,20 +1,6 @@ -use crate::prelude::*; +// FIXME: we want: #![cfg(test)] but does not work across crates -/// Trait for executing network requests, to be implemented on the FFI side -/// (iOS/Android), so that Sargon can with some HTTP client perform make network -/// requests. -#[uniffi::export(with_foreign)] -#[async_trait::async_trait] -pub trait NetworkAntenna: Send + Sync { - /// Method invoked by Sargon Rust side, telling an implementing type to - /// execute a `NetworkRequest` and pass the Result back to Sargon Rust side. - /// - /// Either: `Err` or `Ok(NetworkResponse)`. - async fn execute_network_request( - &self, - request: NetworkRequest, - ) -> Result; -} +use crate::prelude::*; /// A mocked network antenna, useful for testing. #[derive(Debug)] @@ -56,7 +42,7 @@ impl MockAntenna { } #[async_trait::async_trait] -impl NetworkAntenna for MockAntenna { +impl NetworkingDriver for MockAntenna { async fn execute_network_request( &self, request: NetworkRequest, diff --git a/crates/drivers/src/drivers/networking_driver/support/test/mod.rs b/crates/drivers/src/drivers/networking_driver/support/test/mod.rs new file mode 100644 index 000000000..52b0f8eef --- /dev/null +++ b/crates/drivers/src/drivers/networking_driver/support/test/mod.rs @@ -0,0 +1,9 @@ +mod rust_networking_driver; + +pub use rust_networking_driver::*; + +// #[cfg(test)] FIXME +mod mock_networking_driver; + +// #[cfg(test)] FIXME +pub use mock_networking_driver::*; diff --git a/tests/integration/network_antenna_reqwest.rs b/crates/drivers/src/drivers/networking_driver/support/test/rust_networking_driver.rs similarity index 50% rename from tests/integration/network_antenna_reqwest.rs rename to crates/drivers/src/drivers/networking_driver/support/test/rust_networking_driver.rs index 899d4ef75..7f1068aee 100644 --- a/tests/integration/network_antenna_reqwest.rs +++ b/crates/drivers/src/drivers/networking_driver/support/test/rust_networking_driver.rs @@ -1,21 +1,36 @@ +use crate::prelude::*; + use async_trait::async_trait; use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; -use sargon::prelude::*; use std::sync::Arc; -fn map_method(method: NetworkMethod) -> reqwest::Method { - match method { - NetworkMethod::Post => reqwest::Method::POST, - NetworkMethod::Get => reqwest::Method::GET, +impl From for reqwest::Method { + fn from(value: NetworkMethod) -> Self { + match value { + NetworkMethod::Post => reqwest::Method::POST, + NetworkMethod::Get => reqwest::Method::GET, + NetworkMethod::Head => reqwest::Method::HEAD, + } } } -struct RustAntenna { +/// A **Rust** networking driver using `reqwest`. +#[derive(Debug)] +pub struct RustNetworkingDriver { client: reqwest::Client, } +impl RustNetworkingDriver { + pub fn new() -> Arc { + let reqwest_client = reqwest::Client::new(); + Arc::new(Self { + client: reqwest_client, + }) + } +} + #[async_trait] -impl NetworkAntenna for RustAntenna { +impl NetworkingDriver for RustNetworkingDriver { async fn execute_network_request( &self, request: NetworkRequest, @@ -29,7 +44,7 @@ impl NetworkAntenna for RustAntenna { } let request = self .client - .request(map_method(request.method), request.url) + .request(reqwest::Method::from(request.method), request.url) .body(request.body.to_vec()) .headers(headers) .build() @@ -50,10 +65,18 @@ impl NetworkAntenna for RustAntenna { } } -pub(crate) fn new_gateway_client(network_id: NetworkID) -> GatewayClient { - let reqwest_client = reqwest::Client::new(); - let network_antenna = RustAntenna { - client: reqwest_client, - }; - GatewayClient::new(Arc::new(network_antenna), network_id) +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_map_method() { + let test = |m: NetworkMethod, exp: reqwest::Method| { + assert_eq!(reqwest::Method::from(m), exp); + }; + test(NetworkMethod::Post, reqwest::Method::POST); + test(NetworkMethod::Get, reqwest::Method::GET); + test(NetworkMethod::Head, reqwest::Method::HEAD); + } } diff --git a/crates/drivers/src/drivers/secure_storage_driver/mod.rs b/crates/drivers/src/drivers/secure_storage_driver/mod.rs new file mode 100644 index 000000000..ecb97ae2b --- /dev/null +++ b/crates/drivers/src/drivers/secure_storage_driver/mod.rs @@ -0,0 +1,5 @@ +mod secure_storage_driver; +mod support; + +pub use secure_storage_driver::*; +pub use support::*; diff --git a/crates/drivers/src/drivers/secure_storage_driver/secure_storage_driver.rs b/crates/drivers/src/drivers/secure_storage_driver/secure_storage_driver.rs new file mode 100644 index 000000000..ba01ebebd --- /dev/null +++ b/crates/drivers/src/drivers/secure_storage_driver/secure_storage_driver.rs @@ -0,0 +1,18 @@ +use crate::prelude::*; + +#[uniffi::export(with_foreign)] +#[async_trait::async_trait] +pub trait SecureStorageDriver: Send + Sync + std::fmt::Debug { + async fn load_data( + &self, + key: SecureStorageKey, + ) -> Result>; + + async fn save_data( + &self, + key: SecureStorageKey, + data: BagOfBytes, + ) -> Result<()>; + + async fn delete_data_for_key(&self, key: SecureStorageKey) -> Result<()>; +} diff --git a/crates/drivers/src/drivers/secure_storage_driver/support/mod.rs b/crates/drivers/src/drivers/secure_storage_driver/support/mod.rs new file mode 100644 index 000000000..42cb13564 --- /dev/null +++ b/crates/drivers/src/drivers/secure_storage_driver/support/mod.rs @@ -0,0 +1,5 @@ +mod secure_storage_key; +mod test; + +pub use secure_storage_key::*; +pub use test::*; diff --git a/src/wallet/secure_storage/secure_storage_key.rs b/crates/drivers/src/drivers/secure_storage_driver/support/secure_storage_key.rs similarity index 93% rename from src/wallet/secure_storage/secure_storage_key.rs rename to crates/drivers/src/drivers/secure_storage_driver/support/secure_storage_key.rs index 962ebde76..f6c48b76e 100644 --- a/src/wallet/secure_storage/secure_storage_key.rs +++ b/crates/drivers/src/drivers/secure_storage_driver/support/secure_storage_key.rs @@ -4,6 +4,7 @@ use crate::prelude::*; pub enum SecureStorageKey { SnapshotHeadersList, ActiveProfileID, + DeviceInfo, DeviceFactorSourceMnemonic { factor_source_id: FactorSourceIDFromHash, }, @@ -11,6 +12,7 @@ pub enum SecureStorageKey { profile_id: ProfileID, }, } + impl SecureStorageKey { #[cfg(not(tarpaulin_include))] // false negative pub fn identifier(&self) -> String { @@ -18,8 +20,9 @@ impl SecureStorageKey { "secure_storage_key_{}", match self { SecureStorageKey::ActiveProfileID => - "activeProfileID".to_string(), - SecureStorageKey::SnapshotHeadersList => "headers".to_string(), + "activeProfileID".to_owned(), + SecureStorageKey::SnapshotHeadersList => "headers".to_owned(), + SecureStorageKey::DeviceInfo => "device_info".to_owned(), SecureStorageKey::DeviceFactorSourceMnemonic { factor_source_id, } => format!("device_factor_source_{}", factor_source_id), diff --git a/src/wallet/secure_storage/ephemeral_secure_storage.rs b/crates/drivers/src/drivers/secure_storage_driver/support/test/ephemeral_secure_storage.rs similarity index 66% rename from src/wallet/secure_storage/ephemeral_secure_storage.rs rename to crates/drivers/src/drivers/secure_storage_driver/support/test/ephemeral_secure_storage.rs index df1ed791c..e7bb20ead 100644 --- a/src/wallet/secure_storage/ephemeral_secure_storage.rs +++ b/crates/drivers/src/drivers/secure_storage_driver/support/test/ephemeral_secure_storage.rs @@ -1,4 +1,5 @@ -#![cfg(test)] +// #![cfg(test)] // FIXME + use crate::prelude::*; use std::sync::RwLock; @@ -6,8 +7,9 @@ use std::sync::RwLock; /// Used for testing - a type which saves into memory. #[derive(Debug)] pub struct EphemeralSecureStorage { - pub storage: RwLock>>, + pub storage: RwLock>, } + impl EphemeralSecureStorage { pub fn new() -> Arc { Arc::new(EphemeralSecureStorage { @@ -16,15 +18,23 @@ impl EphemeralSecureStorage { } } -impl SecureStorage for EphemeralSecureStorage { - fn load_data(&self, key: SecureStorageKey) -> Result>> { +#[async_trait::async_trait] +impl SecureStorageDriver for EphemeralSecureStorage { + async fn load_data( + &self, + key: SecureStorageKey, + ) -> Result> { self.storage .try_read() .map_err(|_| CommonError::SecureStorageReadError) .map(|s| s.get(&key).cloned()) } - fn save_data(&self, key: SecureStorageKey, value: Vec) -> Result<()> { + async fn save_data( + &self, + key: SecureStorageKey, + value: BagOfBytes, + ) -> Result<()> { let mut storage = self .storage .try_write() @@ -34,7 +44,7 @@ impl SecureStorage for EphemeralSecureStorage { Ok(()) } - fn delete_data_for_key(&self, key: SecureStorageKey) -> Result<()> { + async fn delete_data_for_key(&self, key: SecureStorageKey) -> Result<()> { let mut storage = self .storage .try_write() diff --git a/crates/drivers/src/drivers/secure_storage_driver/support/test/fail_secure_storage.rs b/crates/drivers/src/drivers/secure_storage_driver/support/test/fail_secure_storage.rs new file mode 100644 index 000000000..368e6eb43 --- /dev/null +++ b/crates/drivers/src/drivers/secure_storage_driver/support/test/fail_secure_storage.rs @@ -0,0 +1,28 @@ +// #![cfg(test)] // FIXME + +use crate::prelude::*; + +#[derive(Debug)] +pub struct AlwaysFailSecureStorage {} + +#[async_trait::async_trait] +impl SecureStorageDriver for AlwaysFailSecureStorage { + async fn load_data( + &self, + _key: SecureStorageKey, + ) -> Result> { + panic!("AlwaysFailStorage does not implement `load_data"); + } + + async fn save_data( + &self, + _key: SecureStorageKey, + _data: BagOfBytes, + ) -> Result<()> { + Err(CommonError::Unknown) + } + + async fn delete_data_for_key(&self, _key: SecureStorageKey) -> Result<()> { + panic!("AlwaysFailStorage does not implement `delete_data_for_key"); + } +} diff --git a/crates/drivers/src/drivers/secure_storage_driver/support/test/mod.rs b/crates/drivers/src/drivers/secure_storage_driver/support/test/mod.rs new file mode 100644 index 000000000..289c4436d --- /dev/null +++ b/crates/drivers/src/drivers/secure_storage_driver/support/test/mod.rs @@ -0,0 +1,11 @@ +// #[cfg(test)] // FIXME +mod ephemeral_secure_storage; + +// #[cfg(test)] // FIXME +pub use ephemeral_secure_storage::*; + +// #[cfg(test)] // FIXME +mod fail_secure_storage; + +// #[cfg(test)] // FIXME +pub use fail_secure_storage::*; diff --git a/crates/drivers/src/drivers/unsafe_storage_driver/mod.rs b/crates/drivers/src/drivers/unsafe_storage_driver/mod.rs new file mode 100644 index 000000000..6f454aeba --- /dev/null +++ b/crates/drivers/src/drivers/unsafe_storage_driver/mod.rs @@ -0,0 +1,5 @@ +mod support; +mod unsafe_storage_driver; + +pub use support::*; +pub use unsafe_storage_driver::*; diff --git a/crates/drivers/src/drivers/unsafe_storage_driver/support/mod.rs b/crates/drivers/src/drivers/unsafe_storage_driver/support/mod.rs new file mode 100644 index 000000000..25fb683bc --- /dev/null +++ b/crates/drivers/src/drivers/unsafe_storage_driver/support/mod.rs @@ -0,0 +1,5 @@ +mod test; +mod unsafe_storage_key; + +pub use test::*; +pub use unsafe_storage_key::*; diff --git a/crates/drivers/src/drivers/unsafe_storage_driver/support/test/ephemeral_unsafe_storage.rs b/crates/drivers/src/drivers/unsafe_storage_driver/support/test/ephemeral_unsafe_storage.rs new file mode 100644 index 000000000..dfbb2ccd8 --- /dev/null +++ b/crates/drivers/src/drivers/unsafe_storage_driver/support/test/ephemeral_unsafe_storage.rs @@ -0,0 +1,52 @@ +// #![cfg(test)] // FIXME: cfg(test) multicrate + +use crate::prelude::*; + +use std::sync::RwLock; + +/// Used for testing - a type which saves into memory. +#[derive(Debug)] +pub struct EphemeralUnsafeStorage { + pub storage: RwLock>, +} + +impl EphemeralUnsafeStorage { + pub fn new() -> Arc { + Arc::new(EphemeralUnsafeStorage { + storage: RwLock::new(HashMap::new()), + }) + } +} + +impl UnsafeStorageDriver for EphemeralUnsafeStorage { + fn load_data(&self, key: UnsafeStorageKey) -> Result> { + self.storage + .try_read() + .map_err(|_| CommonError::UnsafeStorageReadError) + .map(|s| s.get(&key).cloned()) + } + + fn save_data( + &self, + key: UnsafeStorageKey, + value: BagOfBytes, + ) -> Result<()> { + let mut storage = self + .storage + .try_write() + .map_err(|_| CommonError::UnsafeStorageWriteError)?; + + storage.insert(key, value); + Ok(()) + } + + fn delete_data_for_key(&self, key: UnsafeStorageKey) -> Result<()> { + let mut storage = self + .storage + .try_write() + .map_err(|_| CommonError::UnsafeStorageWriteError)?; + + storage.remove_entry(&key); + Ok(()) + } +} diff --git a/crates/drivers/src/drivers/unsafe_storage_driver/support/test/fail_unsafe_storage.rs b/crates/drivers/src/drivers/unsafe_storage_driver/support/test/fail_unsafe_storage.rs new file mode 100644 index 000000000..4a8a4894f --- /dev/null +++ b/crates/drivers/src/drivers/unsafe_storage_driver/support/test/fail_unsafe_storage.rs @@ -0,0 +1,24 @@ +// #![cfg(test)] // FIXME: multi crate cf test + +use crate::prelude::*; + +#[derive(Debug)] +pub struct AlwaysFailUnsafeStorage {} + +impl UnsafeStorageDriver for AlwaysFailUnsafeStorage { + fn load_data(&self, _key: UnsafeStorageKey) -> Result> { + panic!("AlwaysFailStorage does not implement `load_data"); + } + + fn save_data( + &self, + _key: UnsafeStorageKey, + _data: BagOfBytes, + ) -> Result<()> { + Err(CommonError::Unknown) + } + + fn delete_data_for_key(&self, _key: UnsafeStorageKey) -> Result<()> { + panic!("AlwaysFailStorage does not implement `delete_data_for_key"); + } +} diff --git a/crates/drivers/src/drivers/unsafe_storage_driver/support/test/mod.rs b/crates/drivers/src/drivers/unsafe_storage_driver/support/test/mod.rs new file mode 100644 index 000000000..cbd7f6bde --- /dev/null +++ b/crates/drivers/src/drivers/unsafe_storage_driver/support/test/mod.rs @@ -0,0 +1,11 @@ +// #[cfg(test)] // FIXME cfg(test) multicrate +mod ephemeral_unsafe_storage; + +// #[cfg(test)] // FIXME cfg(test) multicrate +pub use ephemeral_unsafe_storage::*; + +// #[cfg(test)] // FIXME cfg(test) multicrate +mod fail_unsafe_storage; + +// #[cfg(test)] // FIXME cfg(test) multicrate +pub use fail_unsafe_storage::*; diff --git a/crates/drivers/src/drivers/unsafe_storage_driver/support/unsafe_storage_key.rs b/crates/drivers/src/drivers/unsafe_storage_driver/support/unsafe_storage_key.rs new file mode 100644 index 000000000..911cfb71b --- /dev/null +++ b/crates/drivers/src/drivers/unsafe_storage_driver/support/unsafe_storage_key.rs @@ -0,0 +1,38 @@ +use crate::prelude::*; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, uniffi::Enum)] +pub enum UnsafeStorageKey { + FactorSourceUserHasWrittenDown, +} + +impl UnsafeStorageKey { + pub fn identifier(&self) -> String { + format!( + "unsafe_storage_key_{}", + match self { + UnsafeStorageKey::FactorSourceUserHasWrittenDown => + "factor_source_user_has_written_down".to_owned(), + } + ) + } +} + +#[uniffi::export] +pub fn unsafe_storage_key_identifier(key: &UnsafeStorageKey) -> String { + key.identifier() +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn identifier() { + assert_eq!( + unsafe_storage_key_identifier( + &UnsafeStorageKey::FactorSourceUserHasWrittenDown + ), + "unsafe_storage_key_factor_source_user_has_written_down" + ); + } +} diff --git a/crates/drivers/src/drivers/unsafe_storage_driver/unsafe_storage_driver.rs b/crates/drivers/src/drivers/unsafe_storage_driver/unsafe_storage_driver.rs new file mode 100644 index 000000000..a3b51d9a1 --- /dev/null +++ b/crates/drivers/src/drivers/unsafe_storage_driver/unsafe_storage_driver.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; + +#[uniffi::export(with_foreign)] +pub trait UnsafeStorageDriver: Send + Sync + std::fmt::Debug { + fn load_data(&self, key: UnsafeStorageKey) -> Result>; + + fn save_data(&self, key: UnsafeStorageKey, data: BagOfBytes) -> Result<()>; + + fn delete_data_for_key(&self, key: UnsafeStorageKey) -> Result<()>; +} diff --git a/crates/drivers/src/lib.rs b/crates/drivers/src/lib.rs new file mode 100644 index 000000000..8cf905343 --- /dev/null +++ b/crates/drivers/src/lib.rs @@ -0,0 +1,17 @@ +mod drivers; + +uniffi::remote_type!(Uuid, sargoncommon); +uniffi::remote_type!(Timestamp, sargoncommon); +uniffi::remote_type!(Url, sargoncommon); + +pub mod prelude { + + pub use crate::drivers::*; + + pub use profile::prelude::*; + pub use sargoncommon::prelude::Result; +} + +pub use prelude::*; + +uniffi::include_scaffolding!("drivers"); diff --git a/crates/drivers/uniffi.toml b/crates/drivers/uniffi.toml new file mode 100644 index 000000000..a48702182 --- /dev/null +++ b/crates/drivers/uniffi.toml @@ -0,0 +1,8 @@ +namespace = "drivers" + +[bindings.swift] +module_name = "SargonDrivers" +experimental_sendable_value_types = true + +[bindings.kotlin] +package_name = "com.radixdlt.sargon.drivers" diff --git a/crates/gateway_models/Cargo.toml b/crates/gateway_models/Cargo.toml new file mode 100644 index 000000000..07667a868 --- /dev/null +++ b/crates/gateway_models/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "gateway_models" +version = "1.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } +serde = { workspace = true } +derive_more = { workspace = true } +sargoncommon = { path = "../common" } +ret = { path = "../ret" } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } + +[lib] +crate-type = ["staticlib", "cdylib", "lib"] diff --git a/crates/gateway_models/build.rs b/crates/gateway_models/build.rs new file mode 100644 index 000000000..1d983af01 --- /dev/null +++ b/crates/gateway_models/build.rs @@ -0,0 +1,22 @@ +use std::path::Path; + +pub fn main() { + // Paths for reading fixtures used by tests + let fixtures_path = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../fixtures"); + + let fixtures_models_path = fixtures_path.join("models"); + println!( + "cargo:rustc-env=FIXTURES_MODELS={}/", + fixtures_models_path.display() + ); + + let fixtures_gw_models_path = fixtures_models_path.join("gateway"); + println!( + "cargo:rustc-env=FIXTURES_MODELS_GW={}/", + fixtures_gw_models_path.display() + ); + + uniffi::generate_scaffolding("src/gateway_models.udl") + .expect("Should be able to build."); +} diff --git a/crates/gateway_models/src/gateway_models.udl b/crates/gateway_models/src/gateway_models.udl new file mode 100644 index 000000000..bbd26a6b5 --- /dev/null +++ b/crates/gateway_models/src/gateway_models.udl @@ -0,0 +1 @@ +namespace gateway_models {}; diff --git a/crates/gateway_models/src/lib.rs b/crates/gateway_models/src/lib.rs new file mode 100644 index 000000000..7588abb14 --- /dev/null +++ b/crates/gateway_models/src/lib.rs @@ -0,0 +1,12 @@ +mod models; + +pub mod prelude { + + pub use crate::models::*; + + pub use ret::prelude::*; +} + +pub use prelude::*; + +uniffi::include_scaffolding!("gateway_models"); diff --git a/crates/gateway_models/src/models/logic/mod.rs b/crates/gateway_models/src/models/logic/mod.rs new file mode 100644 index 000000000..c7997be85 --- /dev/null +++ b/crates/gateway_models/src/models/logic/mod.rs @@ -0,0 +1,2 @@ +mod request; +mod response; diff --git a/src/gateway_api/models/logic/request/gw_public_key.rs b/crates/gateway_models/src/models/logic/request/gw_public_key.rs similarity index 100% rename from src/gateway_api/models/logic/request/gw_public_key.rs rename to crates/gateway_models/src/models/logic/request/gw_public_key.rs diff --git a/crates/gateway_models/src/models/logic/request/mod.rs b/crates/gateway_models/src/models/logic/request/mod.rs new file mode 100644 index 000000000..fa443444b --- /dev/null +++ b/crates/gateway_models/src/models/logic/request/mod.rs @@ -0,0 +1,3 @@ +mod gw_public_key; +mod state; +mod transaction; diff --git a/crates/gateway_models/src/models/logic/request/state/entity/mod.rs b/crates/gateway_models/src/models/logic/request/state/entity/mod.rs new file mode 100644 index 000000000..afcbb251f --- /dev/null +++ b/crates/gateway_models/src/models/logic/request/state/entity/mod.rs @@ -0,0 +1 @@ +mod state_entity_details; diff --git a/src/gateway_api/models/logic/request/state/entity/state_entity_details.rs b/crates/gateway_models/src/models/logic/request/state/entity/state_entity_details.rs similarity index 98% rename from src/gateway_api/models/logic/request/state/entity/state_entity_details.rs rename to crates/gateway_models/src/models/logic/request/state/entity/state_entity_details.rs index 1932110f4..c690deb83 100644 --- a/src/gateway_api/models/logic/request/state/entity/state_entity_details.rs +++ b/crates/gateway_models/src/models/logic/request/state/entity/state_entity_details.rs @@ -1,3 +1,4 @@ +#[allow(unused)] use crate::prelude::*; #[cfg(test)] diff --git a/crates/gateway_models/src/models/logic/request/state/mod.rs b/crates/gateway_models/src/models/logic/request/state/mod.rs new file mode 100644 index 000000000..8e7f80437 --- /dev/null +++ b/crates/gateway_models/src/models/logic/request/state/mod.rs @@ -0,0 +1 @@ +mod entity; diff --git a/crates/gateway_models/src/models/logic/request/transaction/mod.rs b/crates/gateway_models/src/models/logic/request/transaction/mod.rs new file mode 100644 index 000000000..13aefce4a --- /dev/null +++ b/crates/gateway_models/src/models/logic/request/transaction/mod.rs @@ -0,0 +1,2 @@ +mod preview; +mod submit; diff --git a/crates/gateway_models/src/models/logic/request/transaction/preview/mod.rs b/crates/gateway_models/src/models/logic/request/transaction/preview/mod.rs new file mode 100644 index 000000000..0c0ddb52b --- /dev/null +++ b/crates/gateway_models/src/models/logic/request/transaction/preview/mod.rs @@ -0,0 +1,2 @@ +mod request_flags; +mod transaction_preview; diff --git a/src/gateway_api/models/logic/request/transaction/preview/request_flags.rs b/crates/gateway_models/src/models/logic/request/transaction/preview/request_flags.rs similarity index 100% rename from src/gateway_api/models/logic/request/transaction/preview/request_flags.rs rename to crates/gateway_models/src/models/logic/request/transaction/preview/request_flags.rs diff --git a/src/gateway_api/models/logic/request/transaction/preview/transaction_preview.rs b/crates/gateway_models/src/models/logic/request/transaction/preview/transaction_preview.rs similarity index 100% rename from src/gateway_api/models/logic/request/transaction/preview/transaction_preview.rs rename to crates/gateway_models/src/models/logic/request/transaction/preview/transaction_preview.rs diff --git a/crates/gateway_models/src/models/logic/request/transaction/submit/mod.rs b/crates/gateway_models/src/models/logic/request/transaction/submit/mod.rs new file mode 100644 index 000000000..cdee74a2e --- /dev/null +++ b/crates/gateway_models/src/models/logic/request/transaction/submit/mod.rs @@ -0,0 +1 @@ +mod transaction_submit; diff --git a/src/gateway_api/models/logic/request/transaction/submit/transaction_submit.rs b/crates/gateway_models/src/models/logic/request/transaction/submit/transaction_submit.rs similarity index 100% rename from src/gateway_api/models/logic/request/transaction/submit/transaction_submit.rs rename to crates/gateway_models/src/models/logic/request/transaction/submit/transaction_submit.rs diff --git a/src/gateway_api/models/logic/response/ledger_state.rs b/crates/gateway_models/src/models/logic/response/ledger_state.rs similarity index 100% rename from src/gateway_api/models/logic/response/ledger_state.rs rename to crates/gateway_models/src/models/logic/response/ledger_state.rs diff --git a/crates/gateway_models/src/models/logic/response/mod.rs b/crates/gateway_models/src/models/logic/response/mod.rs new file mode 100644 index 000000000..ebdad2ed1 --- /dev/null +++ b/crates/gateway_models/src/models/logic/response/mod.rs @@ -0,0 +1,4 @@ +mod ledger_state; + +mod state; +mod transaction; diff --git a/src/gateway_api/models/logic/response/state/entity/details/fungible/collection.rs b/crates/gateway_models/src/models/logic/response/state/entity/details/fungible/collection.rs similarity index 100% rename from src/gateway_api/models/logic/response/state/entity/details/fungible/collection.rs rename to crates/gateway_models/src/models/logic/response/state/entity/details/fungible/collection.rs diff --git a/src/gateway_api/models/logic/response/state/entity/details/fungible/collection_item.rs b/crates/gateway_models/src/models/logic/response/state/entity/details/fungible/collection_item.rs similarity index 100% rename from src/gateway_api/models/logic/response/state/entity/details/fungible/collection_item.rs rename to crates/gateway_models/src/models/logic/response/state/entity/details/fungible/collection_item.rs diff --git a/src/gateway_api/models/logic/response/state/entity/details/fungible/collection_item_global.rs b/crates/gateway_models/src/models/logic/response/state/entity/details/fungible/collection_item_global.rs similarity index 100% rename from src/gateway_api/models/logic/response/state/entity/details/fungible/collection_item_global.rs rename to crates/gateway_models/src/models/logic/response/state/entity/details/fungible/collection_item_global.rs diff --git a/crates/gateway_models/src/models/logic/response/state/entity/details/fungible/mod.rs b/crates/gateway_models/src/models/logic/response/state/entity/details/fungible/mod.rs new file mode 100644 index 000000000..2fac1cc77 --- /dev/null +++ b/crates/gateway_models/src/models/logic/response/state/entity/details/fungible/mod.rs @@ -0,0 +1,3 @@ +mod collection; +mod collection_item; +mod collection_item_global; diff --git a/crates/gateway_models/src/models/logic/response/state/entity/details/mod.rs b/crates/gateway_models/src/models/logic/response/state/entity/details/mod.rs new file mode 100644 index 000000000..fc70cfe6d --- /dev/null +++ b/crates/gateway_models/src/models/logic/response/state/entity/details/mod.rs @@ -0,0 +1,3 @@ +mod fungible; +mod state_entity_details_response; +mod state_entity_details_response_item; diff --git a/src/gateway_api/models/logic/response/state/entity/details/state_entity_details_response.rs b/crates/gateway_models/src/models/logic/response/state/entity/details/state_entity_details_response.rs similarity index 99% rename from src/gateway_api/models/logic/response/state/entity/details/state_entity_details_response.rs rename to crates/gateway_models/src/models/logic/response/state/entity/details/state_entity_details_response.rs index d9d564d54..b73a5b293 100644 --- a/src/gateway_api/models/logic/response/state/entity/details/state_entity_details_response.rs +++ b/crates/gateway_models/src/models/logic/response/state/entity/details/state_entity_details_response.rs @@ -1,3 +1,4 @@ +#[allow(unused)] use crate::prelude::*; #[cfg(test)] diff --git a/src/gateway_api/models/logic/response/state/entity/details/state_entity_details_response_item.rs b/crates/gateway_models/src/models/logic/response/state/entity/details/state_entity_details_response_item.rs similarity index 100% rename from src/gateway_api/models/logic/response/state/entity/details/state_entity_details_response_item.rs rename to crates/gateway_models/src/models/logic/response/state/entity/details/state_entity_details_response_item.rs diff --git a/crates/gateway_models/src/models/logic/response/state/entity/mod.rs b/crates/gateway_models/src/models/logic/response/state/entity/mod.rs new file mode 100644 index 000000000..3e662e16b --- /dev/null +++ b/crates/gateway_models/src/models/logic/response/state/entity/mod.rs @@ -0,0 +1 @@ +mod details; diff --git a/crates/gateway_models/src/models/logic/response/state/mod.rs b/crates/gateway_models/src/models/logic/response/state/mod.rs new file mode 100644 index 000000000..8e7f80437 --- /dev/null +++ b/crates/gateway_models/src/models/logic/response/state/mod.rs @@ -0,0 +1 @@ +mod entity; diff --git a/crates/gateway_models/src/models/logic/response/transaction/construction/mod.rs b/crates/gateway_models/src/models/logic/response/transaction/construction/mod.rs new file mode 100644 index 000000000..ae916587e --- /dev/null +++ b/crates/gateway_models/src/models/logic/response/transaction/construction/mod.rs @@ -0,0 +1 @@ +mod transaction_construction_response; diff --git a/src/gateway_api/models/logic/response/transaction/construction/transaction_construction_response.rs b/crates/gateway_models/src/models/logic/response/transaction/construction/transaction_construction_response.rs similarity index 90% rename from src/gateway_api/models/logic/response/transaction/construction/transaction_construction_response.rs rename to crates/gateway_models/src/models/logic/response/transaction/construction/transaction_construction_response.rs index 40888dab5..644fcc884 100644 --- a/src/gateway_api/models/logic/response/transaction/construction/transaction_construction_response.rs +++ b/crates/gateway_models/src/models/logic/response/transaction/construction/transaction_construction_response.rs @@ -1,4 +1,5 @@ -use crate::prelude::*; +#[allow(unused)] +pub use crate::prelude::*; #[cfg(test)] mod tests { diff --git a/src/gateway_api/models/types/response/transaction/mod.rs b/crates/gateway_models/src/models/logic/response/transaction/mod.rs similarity index 67% rename from src/gateway_api/models/types/response/transaction/mod.rs rename to crates/gateway_models/src/models/logic/response/transaction/mod.rs index 5aecf5d43..39cdfbd9b 100644 --- a/src/gateway_api/models/types/response/transaction/mod.rs +++ b/crates/gateway_models/src/models/logic/response/transaction/mod.rs @@ -2,6 +2,9 @@ mod construction; mod preview; mod submit; +#[allow(unused)] pub use construction::*; +#[allow(unused)] pub use preview::*; +#[allow(unused)] pub use submit::*; diff --git a/src/gateway_api/models/logic/response/transaction/preview/logs_inner.rs b/crates/gateway_models/src/models/logic/response/transaction/preview/logs_inner.rs similarity index 100% rename from src/gateway_api/models/logic/response/transaction/preview/logs_inner.rs rename to crates/gateway_models/src/models/logic/response/transaction/preview/logs_inner.rs diff --git a/crates/gateway_models/src/models/logic/response/transaction/preview/mod.rs b/crates/gateway_models/src/models/logic/response/transaction/preview/mod.rs new file mode 100644 index 000000000..8e3edbc03 --- /dev/null +++ b/crates/gateway_models/src/models/logic/response/transaction/preview/mod.rs @@ -0,0 +1,2 @@ +mod logs_inner; +mod transaction_preview_response; diff --git a/src/gateway_api/models/logic/response/transaction/preview/transaction_preview_response.rs b/crates/gateway_models/src/models/logic/response/transaction/preview/transaction_preview_response.rs similarity index 91% rename from src/gateway_api/models/logic/response/transaction/preview/transaction_preview_response.rs rename to crates/gateway_models/src/models/logic/response/transaction/preview/transaction_preview_response.rs index f98d18eef..cde80bdd0 100644 --- a/src/gateway_api/models/logic/response/transaction/preview/transaction_preview_response.rs +++ b/crates/gateway_models/src/models/logic/response/transaction/preview/transaction_preview_response.rs @@ -1,4 +1,5 @@ -use crate::prelude::*; +#[allow(unused)] +pub use crate::prelude::*; #[cfg(test)] mod tests { diff --git a/crates/gateway_models/src/models/logic/response/transaction/submit/mod.rs b/crates/gateway_models/src/models/logic/response/transaction/submit/mod.rs new file mode 100644 index 000000000..1f891f33e --- /dev/null +++ b/crates/gateway_models/src/models/logic/response/transaction/submit/mod.rs @@ -0,0 +1 @@ +mod submit; diff --git a/src/gateway_api/models/logic/response/transaction/submit/submit.rs b/crates/gateway_models/src/models/logic/response/transaction/submit/submit.rs similarity index 91% rename from src/gateway_api/models/logic/response/transaction/submit/submit.rs rename to crates/gateway_models/src/models/logic/response/transaction/submit/submit.rs index cf0c30f21..94bc550fe 100644 --- a/src/gateway_api/models/logic/response/transaction/submit/submit.rs +++ b/crates/gateway_models/src/models/logic/response/transaction/submit/submit.rs @@ -1,4 +1,5 @@ -use crate::prelude::*; +#[allow(unused)] +pub use crate::prelude::*; #[cfg(test)] mod tests { diff --git a/src/gateway_api/models/mod.rs b/crates/gateway_models/src/models/mod.rs similarity index 69% rename from src/gateway_api/models/mod.rs rename to crates/gateway_models/src/models/mod.rs index 87aa1beec..0b9000767 100644 --- a/src/gateway_api/models/mod.rs +++ b/crates/gateway_models/src/models/mod.rs @@ -1,5 +1,4 @@ mod logic; mod types; -pub use logic::*; pub use types::*; diff --git a/src/gateway_api/models/logic/mod.rs b/crates/gateway_models/src/models/types/mod.rs similarity index 100% rename from src/gateway_api/models/logic/mod.rs rename to crates/gateway_models/src/models/types/mod.rs diff --git a/src/gateway_api/models/types/request/gw_public_key.rs b/crates/gateway_models/src/models/types/request/gw_public_key.rs similarity index 84% rename from src/gateway_api/models/types/request/gw_public_key.rs rename to crates/gateway_models/src/models/types/request/gw_public_key.rs index c63f947ef..22da03c1c 100644 --- a/src/gateway_api/models/types/request/gw_public_key.rs +++ b/crates/gateway_models/src/models/types/request/gw_public_key.rs @@ -1,7 +1,7 @@ use crate::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, EnumAsInner, uniffi::Enum)] -pub(crate) enum GWPublicKey { +pub enum GWPublicKey { Secp256k1(Secp256k1PublicKey), Ed25519(Ed25519PublicKey), } diff --git a/src/gateway_api/models/logic/request/mod.rs b/crates/gateway_models/src/models/types/request/mod.rs similarity index 100% rename from src/gateway_api/models/logic/request/mod.rs rename to crates/gateway_models/src/models/types/request/mod.rs diff --git a/src/gateway_api/models/logic/request/state/entity/mod.rs b/crates/gateway_models/src/models/types/request/state/entity/mod.rs similarity index 100% rename from src/gateway_api/models/logic/request/state/entity/mod.rs rename to crates/gateway_models/src/models/types/request/state/entity/mod.rs diff --git a/src/gateway_api/models/types/request/state/entity/state_entity_details.rs b/crates/gateway_models/src/models/types/request/state/entity/state_entity_details.rs similarity index 84% rename from src/gateway_api/models/types/request/state/entity/state_entity_details.rs rename to crates/gateway_models/src/models/types/request/state/entity/state_entity_details.rs index c4b74bd65..f49f2cabb 100644 --- a/src/gateway_api/models/types/request/state/entity/state_entity_details.rs +++ b/crates/gateway_models/src/models/types/request/state/entity/state_entity_details.rs @@ -9,5 +9,5 @@ use crate::prelude::*; Deserialize, /* Deserialize so we can test roundtrip of JSON vectors */ )] pub struct StateEntityDetailsRequest { - pub(crate) addresses: Vec
, + pub addresses: Vec
, } diff --git a/src/gateway_api/models/logic/request/state/mod.rs b/crates/gateway_models/src/models/types/request/state/mod.rs similarity index 100% rename from src/gateway_api/models/logic/request/state/mod.rs rename to crates/gateway_models/src/models/types/request/state/mod.rs diff --git a/src/gateway_api/models/logic/request/transaction/mod.rs b/crates/gateway_models/src/models/types/request/transaction/mod.rs similarity index 100% rename from src/gateway_api/models/logic/request/transaction/mod.rs rename to crates/gateway_models/src/models/types/request/transaction/mod.rs diff --git a/src/gateway_api/models/logic/request/transaction/preview/mod.rs b/crates/gateway_models/src/models/types/request/transaction/preview/mod.rs similarity index 100% rename from src/gateway_api/models/logic/request/transaction/preview/mod.rs rename to crates/gateway_models/src/models/types/request/transaction/preview/mod.rs diff --git a/src/gateway_api/models/types/request/transaction/preview/request_flags.rs b/crates/gateway_models/src/models/types/request/transaction/preview/request_flags.rs similarity index 64% rename from src/gateway_api/models/types/request/transaction/preview/request_flags.rs rename to crates/gateway_models/src/models/types/request/transaction/preview/request_flags.rs index 1e8a16a2c..1c1eae9d3 100644 --- a/src/gateway_api/models/types/request/transaction/preview/request_flags.rs +++ b/crates/gateway_models/src/models/types/request/transaction/preview/request_flags.rs @@ -10,7 +10,7 @@ use crate::prelude::*; Deserialize, /* Deserialize so we can test roundtrip of JSON vectors */ )] pub struct TransactionPreviewRequestFlags { - pub(crate) use_free_credit: bool, - pub(crate) assume_all_signature_proofs: bool, - pub(crate) skip_epoch_check: bool, + pub use_free_credit: bool, + pub assume_all_signature_proofs: bool, + pub skip_epoch_check: bool, } diff --git a/src/gateway_api/models/types/request/transaction/preview/transaction_preview.rs b/crates/gateway_models/src/models/types/request/transaction/preview/transaction_preview.rs similarity index 66% rename from src/gateway_api/models/types/request/transaction/preview/transaction_preview.rs rename to crates/gateway_models/src/models/types/request/transaction/preview/transaction_preview.rs index 0736c70da..0790d3c19 100644 --- a/src/gateway_api/models/types/request/transaction/preview/transaction_preview.rs +++ b/crates/gateway_models/src/models/types/request/transaction/preview/transaction_preview.rs @@ -8,32 +8,32 @@ use crate::prelude::*; PartialEq, Eq, )] -pub(crate) struct TransactionPreviewRequest { +pub struct TransactionPreviewRequest { /** A text-representation of a transaction manifest */ - pub(crate) manifest: String, + pub manifest: String, /** An array of hex-encoded blob data (optional) */ - pub(crate) blobs_hex: Option>, + pub blobs_hex: Option>, /** An integer between `0` and `10^10`, marking the epoch at which the transaction starts being valid */ - pub(crate) start_epoch_inclusive: u64, + pub start_epoch_inclusive: u64, /** An integer between `0` and `10^10`, marking the epoch at which the transaction is no longer valid */ - pub(crate) end_epoch_exclusive: u64, + pub end_epoch_exclusive: u64, - pub(crate) notary_public_key: Option, + pub notary_public_key: Option, /** Whether the notary should count as a signatory (optional, default false) */ - pub(crate) notary_is_signatory: bool, + pub notary_is_signatory: bool, /** An integer between `0` and `65535`, giving the validator tip as a percentage amount. A value of `1` corresponds to 1% of the fee. */ - pub(crate) tip_percentage: u16, + pub tip_percentage: u16, /** A decimal-string-encoded integer between `0` and `2^32 - 1`, used to ensure the transaction intent is unique. */ - pub(crate) nonce: u32, + pub nonce: u32, /** A list of public keys to be used as transaction signers */ - pub(crate) signer_public_keys: Vec, + pub signer_public_keys: Vec, - pub(crate) flags: TransactionPreviewRequestFlags, + pub flags: TransactionPreviewRequestFlags, } diff --git a/src/gateway_api/models/logic/request/transaction/submit/mod.rs b/crates/gateway_models/src/models/types/request/transaction/submit/mod.rs similarity index 100% rename from src/gateway_api/models/logic/request/transaction/submit/mod.rs rename to crates/gateway_models/src/models/types/request/transaction/submit/mod.rs diff --git a/src/gateway_api/models/types/request/transaction/submit/transaction_submit.rs b/crates/gateway_models/src/models/types/request/transaction/submit/transaction_submit.rs similarity index 85% rename from src/gateway_api/models/types/request/transaction/submit/transaction_submit.rs rename to crates/gateway_models/src/models/types/request/transaction/submit/transaction_submit.rs index 61f833c6e..c294c8b1a 100644 --- a/src/gateway_api/models/types/request/transaction/submit/transaction_submit.rs +++ b/crates/gateway_models/src/models/types/request/transaction/submit/transaction_submit.rs @@ -10,5 +10,5 @@ use crate::prelude::*; )] pub struct TransactionSubmitRequest { /** Hex-encoded notarized transaction payload which can be submitted. */ - pub(crate) notarized_transaction_hex: String, + pub notarized_transaction_hex: String, } diff --git a/src/gateway_api/models/types/response/ledger_state.rs b/crates/gateway_models/src/models/types/response/ledger_state.rs similarity index 100% rename from src/gateway_api/models/types/response/ledger_state.rs rename to crates/gateway_models/src/models/types/response/ledger_state.rs diff --git a/src/gateway_api/models/logic/response/mod.rs b/crates/gateway_models/src/models/types/response/mod.rs similarity index 100% rename from src/gateway_api/models/logic/response/mod.rs rename to crates/gateway_models/src/models/types/response/mod.rs diff --git a/src/gateway_api/models/types/response/state/entity/details/fungible/collection.rs b/crates/gateway_models/src/models/types/response/state/entity/details/fungible/collection.rs similarity index 100% rename from src/gateway_api/models/types/response/state/entity/details/fungible/collection.rs rename to crates/gateway_models/src/models/types/response/state/entity/details/fungible/collection.rs diff --git a/src/gateway_api/models/types/response/state/entity/details/fungible/collection_item.rs b/crates/gateway_models/src/models/types/response/state/entity/details/fungible/collection_item.rs similarity index 100% rename from src/gateway_api/models/types/response/state/entity/details/fungible/collection_item.rs rename to crates/gateway_models/src/models/types/response/state/entity/details/fungible/collection_item.rs diff --git a/src/gateway_api/models/types/response/state/entity/details/fungible/collection_item_global.rs b/crates/gateway_models/src/models/types/response/state/entity/details/fungible/collection_item_global.rs similarity index 100% rename from src/gateway_api/models/types/response/state/entity/details/fungible/collection_item_global.rs rename to crates/gateway_models/src/models/types/response/state/entity/details/fungible/collection_item_global.rs diff --git a/src/gateway_api/models/logic/response/state/entity/details/fungible/mod.rs b/crates/gateway_models/src/models/types/response/state/entity/details/fungible/mod.rs similarity index 100% rename from src/gateway_api/models/logic/response/state/entity/details/fungible/mod.rs rename to crates/gateway_models/src/models/types/response/state/entity/details/fungible/mod.rs diff --git a/src/gateway_api/models/logic/response/state/entity/details/mod.rs b/crates/gateway_models/src/models/types/response/state/entity/details/mod.rs similarity index 100% rename from src/gateway_api/models/logic/response/state/entity/details/mod.rs rename to crates/gateway_models/src/models/types/response/state/entity/details/mod.rs diff --git a/src/gateway_api/models/types/response/state/entity/details/state_entity_details_response.rs b/crates/gateway_models/src/models/types/response/state/entity/details/state_entity_details_response.rs similarity index 100% rename from src/gateway_api/models/types/response/state/entity/details/state_entity_details_response.rs rename to crates/gateway_models/src/models/types/response/state/entity/details/state_entity_details_response.rs diff --git a/src/gateway_api/models/types/response/state/entity/details/state_entity_details_response_item.rs b/crates/gateway_models/src/models/types/response/state/entity/details/state_entity_details_response_item.rs similarity index 100% rename from src/gateway_api/models/types/response/state/entity/details/state_entity_details_response_item.rs rename to crates/gateway_models/src/models/types/response/state/entity/details/state_entity_details_response_item.rs diff --git a/src/gateway_api/models/logic/response/state/entity/mod.rs b/crates/gateway_models/src/models/types/response/state/entity/mod.rs similarity index 100% rename from src/gateway_api/models/logic/response/state/entity/mod.rs rename to crates/gateway_models/src/models/types/response/state/entity/mod.rs diff --git a/src/gateway_api/models/logic/response/state/mod.rs b/crates/gateway_models/src/models/types/response/state/mod.rs similarity index 100% rename from src/gateway_api/models/logic/response/state/mod.rs rename to crates/gateway_models/src/models/types/response/state/mod.rs diff --git a/src/gateway_api/models/logic/response/transaction/construction/mod.rs b/crates/gateway_models/src/models/types/response/transaction/construction/mod.rs similarity index 100% rename from src/gateway_api/models/logic/response/transaction/construction/mod.rs rename to crates/gateway_models/src/models/types/response/transaction/construction/mod.rs diff --git a/src/gateway_api/models/types/response/transaction/construction/transaction_construction_response.rs b/crates/gateway_models/src/models/types/response/transaction/construction/transaction_construction_response.rs similarity index 100% rename from src/gateway_api/models/types/response/transaction/construction/transaction_construction_response.rs rename to crates/gateway_models/src/models/types/response/transaction/construction/transaction_construction_response.rs diff --git a/src/gateway_api/models/logic/response/transaction/mod.rs b/crates/gateway_models/src/models/types/response/transaction/mod.rs similarity index 100% rename from src/gateway_api/models/logic/response/transaction/mod.rs rename to crates/gateway_models/src/models/types/response/transaction/mod.rs diff --git a/src/gateway_api/models/types/response/transaction/preview/logs_inner.rs b/crates/gateway_models/src/models/types/response/transaction/preview/logs_inner.rs similarity index 100% rename from src/gateway_api/models/types/response/transaction/preview/logs_inner.rs rename to crates/gateway_models/src/models/types/response/transaction/preview/logs_inner.rs diff --git a/src/gateway_api/models/logic/response/transaction/preview/mod.rs b/crates/gateway_models/src/models/types/response/transaction/preview/mod.rs similarity index 100% rename from src/gateway_api/models/logic/response/transaction/preview/mod.rs rename to crates/gateway_models/src/models/types/response/transaction/preview/mod.rs diff --git a/src/gateway_api/models/types/response/transaction/preview/transaction_preview_response.rs b/crates/gateway_models/src/models/types/response/transaction/preview/transaction_preview_response.rs similarity index 100% rename from src/gateway_api/models/types/response/transaction/preview/transaction_preview_response.rs rename to crates/gateway_models/src/models/types/response/transaction/preview/transaction_preview_response.rs diff --git a/src/gateway_api/models/logic/response/transaction/submit/mod.rs b/crates/gateway_models/src/models/types/response/transaction/submit/mod.rs similarity index 100% rename from src/gateway_api/models/logic/response/transaction/submit/mod.rs rename to crates/gateway_models/src/models/types/response/transaction/submit/mod.rs diff --git a/src/gateway_api/models/types/response/transaction/submit/submit.rs b/crates/gateway_models/src/models/types/response/transaction/submit/submit.rs similarity index 100% rename from src/gateway_api/models/types/response/transaction/submit/submit.rs rename to crates/gateway_models/src/models/types/response/transaction/submit/submit.rs diff --git a/crates/gateway_models/uniffi.toml b/crates/gateway_models/uniffi.toml new file mode 100644 index 000000000..e60e691d8 --- /dev/null +++ b/crates/gateway_models/uniffi.toml @@ -0,0 +1,8 @@ +namespace = "gateway_models" + +[bindings.swift] +module_name = "SargonGatewayModels" +experimental_sendable_value_types = true + +[bindings.kotlin] +package_name = "com.radixdlt.sargon.gatewaymodels" diff --git a/crates/hierarchical_deterministic/Cargo.toml b/crates/hierarchical_deterministic/Cargo.toml new file mode 100644 index 000000000..3f4ec2c0c --- /dev/null +++ b/crates/hierarchical_deterministic/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "hd" +version = "1.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } +sargoncommon = { path = "../common" } +iota-crypto = { workspace = true } +k256 = { workspace = true } +bip39 = { workspace = true } +derive_more = { workspace = true } +serde = { workspace = true } +serde_with = { workspace = true } +serde_json = { workspace = true } +zeroize = { workspace = true } +enum-iterator = { workspace = true } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } + +[lib] +crate-type = ["staticlib", "cdylib", "lib"] diff --git a/crates/hierarchical_deterministic/build.rs b/crates/hierarchical_deterministic/build.rs new file mode 100644 index 000000000..21e84dd84 --- /dev/null +++ b/crates/hierarchical_deterministic/build.rs @@ -0,0 +1,4 @@ +pub fn main() { + uniffi::generate_scaffolding("src/hd.udl") + .expect("Should be able to build."); +} diff --git a/src/hierarchical_deterministic/bip32/hd_path.rs b/crates/hierarchical_deterministic/src/bip32/hd_path.rs similarity index 96% rename from src/hierarchical_deterministic/bip32/hd_path.rs rename to crates/hierarchical_deterministic/src/bip32/hd_path.rs index 20b76a195..cc40289a6 100644 --- a/src/hierarchical_deterministic/bip32/hd_path.rs +++ b/crates/hierarchical_deterministic/src/bip32/hd_path.rs @@ -67,7 +67,7 @@ impl HasSampleValues for HDPath { } impl HDPath { - pub(crate) fn from_components(components: I) -> Self + pub fn from_components(components: I) -> Self where I: IntoIterator, { @@ -76,11 +76,11 @@ impl HDPath { } } - pub(crate) fn depth(&self) -> usize { + pub fn depth(&self) -> usize { self.components.len() } - pub(crate) fn parse_try_map( + pub fn parse_try_map( path: &[HDPathComponent], index: usize, try_map: F, @@ -92,7 +92,7 @@ impl HDPath { try_map(got.index()) } - pub(crate) fn parse( + pub fn parse( path: &[HDPathComponent], index: usize, expected: HDPathComponent, @@ -109,7 +109,7 @@ impl HDPath { } #[cfg(not(tarpaulin_include))] // false negative - pub(crate) fn try_parse_base_hdpath( + pub fn try_parse_base_hdpath( path: &HDPath, depth_error: F, ) -> Result<(HDPath, Vec)> @@ -138,7 +138,7 @@ impl HDPath { Ok((path.clone(), components.clone())) } - pub(crate) fn try_parse_base( + pub fn try_parse_base( s: &str, depth_error: F, ) -> Result<(HDPath, Vec)> diff --git a/src/hierarchical_deterministic/bip32/hd_path_component.rs b/crates/hierarchical_deterministic/src/bip32/hd_path_component.rs similarity index 91% rename from src/hierarchical_deterministic/bip32/hd_path_component.rs rename to crates/hierarchical_deterministic/src/bip32/hd_path_component.rs index 0fef1b586..f03d2ab93 100644 --- a/src/hierarchical_deterministic/bip32/hd_path_component.rs +++ b/crates/hierarchical_deterministic/src/bip32/hd_path_component.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -pub(crate) const BIP32_HARDENED: u32 = 2147483648; +pub const BIP32_HARDENED: u32 = 2147483648; pub type HDPathValue = u32; @@ -29,7 +29,7 @@ impl From for HDPathComponent { } impl HDPathComponent { - pub(crate) fn index(&self) -> HDPathValue { + pub fn index(&self) -> HDPathValue { if self.is_hardened() { self.value - BIP32_HARDENED } else { @@ -37,11 +37,11 @@ impl HDPathComponent { } } - pub(crate) fn is_hardened(&self) -> bool { + pub fn is_hardened(&self) -> bool { self.value >= BIP32_HARDENED } - pub(crate) fn try_from_str(s: &str) -> Option { + pub fn try_from_str(s: &str) -> Option { let is_hardened = s.ends_with('H') || s.ends_with('\''); let mut component_str = s; if is_hardened { @@ -56,7 +56,7 @@ impl HDPathComponent { }) } - pub(crate) fn non_hardened(value: HDPathValue) -> Self { + pub fn non_hardened(value: HDPathValue) -> Self { assert!( value < BIP32_HARDENED, "Passed value was hardened, expected it to not be." @@ -64,7 +64,7 @@ impl HDPathComponent { Self { value } } - pub(crate) fn harden(value: HDPathValue) -> Self { + pub fn harden(value: HDPathValue) -> Self { assert!( value < BIP32_HARDENED, "Passed value was already hardened, expected it to not be." diff --git a/src/hierarchical_deterministic/bip32/hd_path_component_uniffi_fn.rs b/crates/hierarchical_deterministic/src/bip32/hd_path_component_uniffi_fn.rs similarity index 100% rename from src/hierarchical_deterministic/bip32/hd_path_component_uniffi_fn.rs rename to crates/hierarchical_deterministic/src/bip32/hd_path_component_uniffi_fn.rs diff --git a/src/hierarchical_deterministic/bip32/mod.rs b/crates/hierarchical_deterministic/src/bip32/mod.rs similarity index 100% rename from src/hierarchical_deterministic/bip32/mod.rs rename to crates/hierarchical_deterministic/src/bip32/mod.rs diff --git a/src/hierarchical_deterministic/bip39/bip39_passphrase.rs b/crates/hierarchical_deterministic/src/bip39/bip39_passphrase.rs similarity index 100% rename from src/hierarchical_deterministic/bip39/bip39_passphrase.rs rename to crates/hierarchical_deterministic/src/bip39/bip39_passphrase.rs diff --git a/crates/hierarchical_deterministic/src/bip39/bip39_seed.rs b/crates/hierarchical_deterministic/src/bip39/bip39_seed.rs new file mode 100644 index 000000000..ed097100e --- /dev/null +++ b/crates/hierarchical_deterministic/src/bip39/bip39_seed.rs @@ -0,0 +1,122 @@ +use crate::prelude::*; + +use crypto::{ + keys::slip10::{self as IotaSlip10, Hardened as IotaSlip10PathComponent}, + signatures::ed25519 as IotaSlip10Ed25519, + signatures::secp256k1_ecdsa as IotaSlip10Secp256k1, +}; + +uniffi::custom_newtype!(BIP39Seed, Exactly64Bytes); + +/// A BIP39 seed for hierarchal deterministic wallets, as per the [BIP39 standard][doc]. +/// +/// We typically obtain this by calling [`to_seed` on `MnemonicWithPassphrase`][MnemonicWithPassphrase::to_seed]. +/// +/// [doc]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#user-content-From_mnemonic_to_seed +#[derive( + Zeroize, + Serialize, + Deserialize, + Clone, + PartialEq, + Eq, + derive_more::Display, + derive_more::Debug, + Hash, +)] +#[serde(transparent)] +#[display("")] +pub struct BIP39Seed(pub Exactly64Bytes); + +impl BIP39Seed { + pub fn is_zeroized(&self) -> bool { + self.0.as_ref() == [0; 64] + } +} + +impl HDPath { + fn hardened_chain(&self) -> Vec { + self.components + .iter() + .map(|c| c.value) + .map(|v| IotaSlip10PathComponent::try_from(v).expect("Should work")) + .collect_vec() + } +} + +impl BIP39Seed { + fn derive_slip10_private_key(&self, chain: I) -> IotaSlip10::Slip10 + where + K: IotaSlip10::IsSecretKey + + IotaSlip10::WithSegment<::Item>, + I: Iterator, + ::Item: IotaSlip10::Segment, + { + let iota_seed = IotaSlip10::Seed::from_bytes(self.0.as_ref()); + iota_seed.derive(chain) + // `IotaSlip10::Seed` implements `ZeroizeOnDrop` so should now be zeroized. + } + + pub fn derive_ed25519_private_key( + &self, + path: &HDPath, + ) -> Ed25519PrivateKey { + let ck = self + .derive_slip10_private_key::( + path.hardened_chain().into_iter(), + ); + Ed25519PrivateKey::from_bytes(ck.secret_key().as_slice()) + .expect("Valid Ed25519PrivateKey bytes") + // `IotaSlip10Ed25519::SecretKey` implements `ZeroizeOnDrop` so should now be zeroized. + } + + pub fn derive_secp256k1_private_key( + &self, + path: &HDPath, + ) -> Secp256k1PrivateKey { + let ck = self + .derive_slip10_private_key::( + path.components.iter().cloned().map(|c| c.value), + ); + Secp256k1PrivateKey::from_bytes(&*ck.secret_key().to_bytes()) + .expect("Valid Secp256k1PrivateKey bytes") + // `IotaSlip10Ed25519::SecretKey` implements `ZeroizeOnDrop` so should now be zeroized. + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[allow(clippy::upper_case_acronyms)] +// type SUT = BIP39Seed; + +// #[test] +// fn manual_uniffi_conversion() { +// let bytes = Exactly64Bytes::sample(); +// let builtin: BagOfBytes = bytes.clone().as_ref().into(); +// let sut = new_b_i_p39_seed_from_bytes(builtin.clone()).unwrap(); +// let rust_side = sut.secret_magic; + +// let ffi_side = +// ::from_custom( +// rust_side, +// ); + +// assert_eq!(ffi_side.to_hex(), builtin.to_hex()); + +// let from_ffi_side = +// ::into_custom( +// ffi_side, +// ) +// .unwrap(); + +// assert_eq!( +// new_b_i_p39_seed_from_bytes(builtin.clone()) +// .unwrap() +// .secret_magic +// .0, +// from_ffi_side.0 +// ); +// } +// } diff --git a/src/hierarchical_deterministic/bip39/bip39_word/bip39_language.rs b/crates/hierarchical_deterministic/src/bip39/bip39_word/bip39_language.rs similarity index 100% rename from src/hierarchical_deterministic/bip39/bip39_word/bip39_language.rs rename to crates/hierarchical_deterministic/src/bip39/bip39_word/bip39_language.rs diff --git a/src/hierarchical_deterministic/bip39/bip39_word/bip39_language_uniffi_fn.rs b/crates/hierarchical_deterministic/src/bip39/bip39_word/bip39_language_uniffi_fn.rs similarity index 100% rename from src/hierarchical_deterministic/bip39/bip39_word/bip39_language_uniffi_fn.rs rename to crates/hierarchical_deterministic/src/bip39/bip39_word/bip39_language_uniffi_fn.rs diff --git a/src/hierarchical_deterministic/bip39/bip39_word/bip39_word.rs b/crates/hierarchical_deterministic/src/bip39/bip39_word/bip39_word.rs similarity index 98% rename from src/hierarchical_deterministic/bip39/bip39_word/bip39_word.rs rename to crates/hierarchical_deterministic/src/bip39/bip39_word/bip39_word.rs index fd888e7fe..294a9eedd 100644 --- a/src/hierarchical_deterministic/bip39/bip39_word/bip39_word.rs +++ b/crates/hierarchical_deterministic/src/bip39/bip39_word/bip39_word.rs @@ -67,7 +67,7 @@ fn word_by_index(u11: U11, language: bip39::Language) -> BIP39Word { } } -pub(crate) fn bip39_word_by_index(u11: U11) -> BIP39Word { +pub fn bip39_word_by_index(u11: U11) -> BIP39Word { word_by_index(u11, bip39::Language::English) } diff --git a/src/hierarchical_deterministic/bip39/bip39_word/bip39_word_uniffi_fn.rs b/crates/hierarchical_deterministic/src/bip39/bip39_word/bip39_word_uniffi_fn.rs similarity index 100% rename from src/hierarchical_deterministic/bip39/bip39_word/bip39_word_uniffi_fn.rs rename to crates/hierarchical_deterministic/src/bip39/bip39_word/bip39_word_uniffi_fn.rs diff --git a/src/hierarchical_deterministic/bip39/bip39_word/mod.rs b/crates/hierarchical_deterministic/src/bip39/bip39_word/mod.rs similarity index 100% rename from src/hierarchical_deterministic/bip39/bip39_word/mod.rs rename to crates/hierarchical_deterministic/src/bip39/bip39_word/mod.rs diff --git a/src/hierarchical_deterministic/bip39/bip39_word/u11.rs b/crates/hierarchical_deterministic/src/bip39/bip39_word/u11.rs similarity index 100% rename from src/hierarchical_deterministic/bip39/bip39_word/u11.rs rename to crates/hierarchical_deterministic/src/bip39/bip39_word/u11.rs diff --git a/src/hierarchical_deterministic/bip39/bip39_word_count.rs b/crates/hierarchical_deterministic/src/bip39/bip39_word_count.rs similarity index 100% rename from src/hierarchical_deterministic/bip39/bip39_word_count.rs rename to crates/hierarchical_deterministic/src/bip39/bip39_word_count.rs diff --git a/src/hierarchical_deterministic/bip39/bip39_word_count_uniffi_fn.rs b/crates/hierarchical_deterministic/src/bip39/bip39_word_count_uniffi_fn.rs similarity index 100% rename from src/hierarchical_deterministic/bip39/bip39_word_count_uniffi_fn.rs rename to crates/hierarchical_deterministic/src/bip39/bip39_word_count_uniffi_fn.rs diff --git a/src/hierarchical_deterministic/bip39/mnemonic.rs b/crates/hierarchical_deterministic/src/bip39/mnemonic.rs similarity index 98% rename from src/hierarchical_deterministic/bip39/mnemonic.rs rename to crates/hierarchical_deterministic/src/bip39/mnemonic.rs index d6d903f38..3584babdc 100644 --- a/src/hierarchical_deterministic/bip39/mnemonic.rs +++ b/crates/hierarchical_deterministic/src/bip39/mnemonic.rs @@ -54,9 +54,7 @@ impl Mnemonic { format!("Mnemonic in {} obfuscated.", self.language) } - pub(crate) fn from_internal(internal: bip39::Mnemonic) -> Self { - use k256::elliptic_curve::zeroize::Zeroize; - + pub fn from_internal(internal: bip39::Mnemonic) -> Self { let language = internal.language(); let words = internal @@ -115,7 +113,8 @@ impl Mnemonic { } pub fn to_seed(&self, passphrase: &str) -> BIP39Seed { - BIP39Seed::new(self.internal().to_seed(passphrase)) + // BIP39Seed::new(self.internal().to_seed(passphrase)) + BIP39Seed(Exactly64Bytes::from(&self.internal().to_seed(passphrase))) } } diff --git a/crates/hierarchical_deterministic/src/bip39/mnemonic_from_entropy.rs b/crates/hierarchical_deterministic/src/bip39/mnemonic_from_entropy.rs new file mode 100644 index 000000000..9cba78be1 --- /dev/null +++ b/crates/hierarchical_deterministic/src/bip39/mnemonic_from_entropy.rs @@ -0,0 +1,81 @@ +use crate::prelude::*; + +impl Mnemonic { + pub fn from_entropy_in( + entropy: NonEmptyMax32Bytes, + language: BIP39Language, + ) -> Result { + let internal = + bip39::Mnemonic::from_entropy_in(language.into(), entropy.as_ref()) + .map_err(|_| CommonError::InvalidMnemonicPhrase)?; + + Ok(Self::from_internal(internal)) + } + + pub fn from_entropy(entropy: NonEmptyMax32Bytes) -> Result { + Self::from_entropy_in(entropy, BIP39Language::English) + } + + pub fn generate_new() -> Self { + Self::from_entropy(NonEmptyMax32Bytes::generate()) + .expect("Should have generated 32 bytes") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Mnemonic; + + #[test] + fn mnemonic_from_entropy_of_16_bytes() { + let sut = + SUT::from_entropy(NonEmptyMax32Bytes::from([0xff; 16])).unwrap(); + assert_eq!( + sut.phrase(), + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong" + ) + } + + #[test] + fn mnemonic_from_entropy_of_20_bytes() { + let sut = + SUT::from_entropy(NonEmptyMax32Bytes::from([0xff; 20])).unwrap(); + assert_eq!( + sut.phrase(), + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrist" + ) + } + + #[test] + fn mnemonic_from_entropy_of_24_bytes() { + let sut = + SUT::from_entropy(NonEmptyMax32Bytes::from([0xff; 24])).unwrap(); + assert_eq!( + sut.phrase(), + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when" + ) + } + + #[test] + fn mnemonic_from_entropy_of_28_bytes() { + let sut = + SUT::from_entropy(NonEmptyMax32Bytes::from([0xff; 28])).unwrap(); + assert_eq!( + sut.phrase(), + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo veteran" + ); + } + + #[test] + fn mnemonic_from_entropy_of_32_bytes() { + let sut = + SUT::from_entropy(NonEmptyMax32Bytes::from([0xff; 32])).unwrap(); + assert_eq!( + sut.phrase(), + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote" + ) + } +} diff --git a/src/hierarchical_deterministic/bip39/mnemonic_uniffi_fn.rs b/crates/hierarchical_deterministic/src/bip39/mnemonic_uniffi_fn.rs similarity index 71% rename from src/hierarchical_deterministic/bip39/mnemonic_uniffi_fn.rs rename to crates/hierarchical_deterministic/src/bip39/mnemonic_uniffi_fn.rs index 308f56710..22db333ac 100644 --- a/src/hierarchical_deterministic/bip39/mnemonic_uniffi_fn.rs +++ b/crates/hierarchical_deterministic/src/bip39/mnemonic_uniffi_fn.rs @@ -1,12 +1,12 @@ use crate::prelude::*; -#[uniffi::export] -pub fn new_mnemonic_generate_with_entropy( - entropy: BIP39Entropy, - language: BIP39Language, -) -> Mnemonic { - Mnemonic::from_entropy_in(entropy, language) -} +// #[uniffi::export] +// pub fn new_mnemonic_generate_with_entropy( +// entropy: BIP39Entropy, +// language: BIP39Language, +// ) -> Mnemonic { +// Mnemonic::from_entropy_in(entropy, language) +// } /// Returns new mnemonic from a string of words #[uniffi::export] @@ -72,27 +72,27 @@ mod uniffi_tests { assert_eq!(mnemonic_phrase(&sut), str); } - #[test] - fn test_new_mnemonic_generate_with_entropy_16_bytes() { - let sut = new_mnemonic_generate_with_entropy( - BIP39Entropy::EntropyOf16Bytes(Entropy16Bytes::new([0xff; 16])), - BIP39Language::English, - ); - assert_eq!( - sut.phrase(), - "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong" - ); - } - - #[test] - fn test_new_mnemonic_generate_with_entropy_32_bytes() { - let sut = new_mnemonic_generate_with_entropy( - BIP39Entropy::EntropyOf32Bytes(Entropy32Bytes::new([0xff; 32])), - BIP39Language::English, - ); - assert_eq!(sut.phrase(), "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", - ); - } + // #[test] + // fn test_new_mnemonic_generate_with_entropy_16_bytes() { + // let sut = new_mnemonic_generate_with_entropy( + // BIP39Entropy::EntropyOf16Bytes(Entropy16Bytes::new([0xff; 16])), + // BIP39Language::English, + // ); + // assert_eq!( + // sut.phrase(), + // "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong" + // ); + // } + + // #[test] + // fn test_new_mnemonic_generate_with_entropy_32_bytes() { + // let sut = new_mnemonic_generate_with_entropy( + // BIP39Entropy::EntropyOf32Bytes(Entropy32Bytes::new([0xff; 32])), + // BIP39Language::English, + // ); + // assert_eq!(sut.phrase(), "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", + // ); + // } #[test] fn test_new_mnemonic_from_phrase() { diff --git a/src/hierarchical_deterministic/bip39/mod.rs b/crates/hierarchical_deterministic/src/bip39/mod.rs similarity index 88% rename from src/hierarchical_deterministic/bip39/mod.rs rename to crates/hierarchical_deterministic/src/bip39/mod.rs index 12377c8f1..327b870bb 100644 --- a/src/hierarchical_deterministic/bip39/mod.rs +++ b/crates/hierarchical_deterministic/src/bip39/mod.rs @@ -1,17 +1,17 @@ -mod bip39_entropy; mod bip39_passphrase; mod bip39_seed; mod bip39_word; mod bip39_word_count; mod bip39_word_count_uniffi_fn; mod mnemonic; +mod mnemonic_from_entropy; mod mnemonic_uniffi_fn; -pub use bip39_entropy::*; pub use bip39_passphrase::*; pub use bip39_seed::*; pub use bip39_word::*; pub use bip39_word_count::*; pub use bip39_word_count_uniffi_fn::*; pub use mnemonic::*; + pub use mnemonic_uniffi_fn::*; diff --git a/src/hierarchical_deterministic/bip44/bip44.rs b/crates/hierarchical_deterministic/src/bip44/bip44.rs similarity index 100% rename from src/hierarchical_deterministic/bip44/bip44.rs rename to crates/hierarchical_deterministic/src/bip44/bip44.rs diff --git a/src/hierarchical_deterministic/bip44/bip44_like_path.rs b/crates/hierarchical_deterministic/src/bip44/bip44_like_path.rs similarity index 94% rename from src/hierarchical_deterministic/bip44/bip44_like_path.rs rename to crates/hierarchical_deterministic/src/bip44/bip44_like_path.rs index 17ef3c085..a1dadd31d 100644 --- a/src/hierarchical_deterministic/bip44/bip44_like_path.rs +++ b/crates/hierarchical_deterministic/src/bip44/bip44_like_path.rs @@ -39,7 +39,7 @@ use crate::prelude::*; derive_more::Display, uniffi::Record, )] -#[display("{}", self.bip32_string())] +#[display("{}", self.path.to_string())] pub struct BIP44LikePath { pub path: HDPath, } @@ -115,27 +115,6 @@ impl BIP44LikePath { } } -impl Derivation for BIP44LikePath { - fn curve(&self) -> SLIP10Curve { - self.scheme().curve() - } - - fn derivation_path(&self) -> DerivationPath { - DerivationPath::BIP44Like { - value: self.clone(), - } - } - fn hd_path(&self) -> &HDPath { - &self.path - } -} - -impl BIP44LikePath { - fn scheme(&self) -> DerivationPathScheme { - DerivationPathScheme::Bip44Olympia - } -} - impl FromStr for BIP44LikePath { type Err = CommonError; diff --git a/src/hierarchical_deterministic/bip44/bip44_like_path_uniffi_fn.rs b/crates/hierarchical_deterministic/src/bip44/bip44_like_path_uniffi_fn.rs similarity index 87% rename from src/hierarchical_deterministic/bip44/bip44_like_path_uniffi_fn.rs rename to crates/hierarchical_deterministic/src/bip44/bip44_like_path_uniffi_fn.rs index 2d0df6e44..e1964c4d4 100644 --- a/src/hierarchical_deterministic/bip44/bip44_like_path_uniffi_fn.rs +++ b/crates/hierarchical_deterministic/src/bip44/bip44_like_path_uniffi_fn.rs @@ -17,11 +17,6 @@ pub fn bip44_like_path_to_string(path: &BIP44LikePath) -> String { path.to_string() } -#[uniffi::export] -pub fn bip44_like_path_get_address_index(path: &BIP44LikePath) -> HDPathValue { - path.last_component().index() -} - #[uniffi::export] pub fn new_bip44_like_path_sample() -> BIP44LikePath { BIP44LikePath::sample() @@ -77,9 +72,4 @@ mod tests { .to_string() ); } - - #[test] - fn test_bip44_like_path_get_address_index() { - assert_eq!(bip44_like_path_get_address_index(&SUT::sample_other()), 1) - } } diff --git a/src/hierarchical_deterministic/bip44/mod.rs b/crates/hierarchical_deterministic/src/bip44/mod.rs similarity index 100% rename from src/hierarchical_deterministic/bip44/mod.rs rename to crates/hierarchical_deterministic/src/bip44/mod.rs diff --git a/crates/hierarchical_deterministic/src/hd.udl b/crates/hierarchical_deterministic/src/hd.udl new file mode 100644 index 000000000..653e1d8ea --- /dev/null +++ b/crates/hierarchical_deterministic/src/hd.udl @@ -0,0 +1 @@ +namespace hd {}; diff --git a/crates/hierarchical_deterministic/src/lib.rs b/crates/hierarchical_deterministic/src/lib.rs new file mode 100644 index 000000000..4f64be00d --- /dev/null +++ b/crates/hierarchical_deterministic/src/lib.rs @@ -0,0 +1,16 @@ +mod bip32; +mod bip39; +mod bip44; + +pub mod prelude { + + pub use crate::bip32::*; + pub use crate::bip39::*; + pub use crate::bip44::*; + + pub use sargoncommon::prelude::*; +} + +pub use prelude::*; + +uniffi::include_scaffolding!("hd"); diff --git a/crates/hierarchical_deterministic/uniffi.toml b/crates/hierarchical_deterministic/uniffi.toml new file mode 100644 index 000000000..d3b827180 --- /dev/null +++ b/crates/hierarchical_deterministic/uniffi.toml @@ -0,0 +1,8 @@ +namespace = "hd" + +[bindings.swift] +module_name = "SargonHD" +experimental_sendable_value_types = true + +[bindings.kotlin] +package_name = "com.radixdlt.sargon.hd" diff --git a/crates/profile/Cargo.toml b/crates/profile/Cargo.toml new file mode 100644 index 000000000..d651ffd55 --- /dev/null +++ b/crates/profile/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "profile" +version = "1.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } +radix-engine-interface = { workspace = true } +radix-rust = { workspace = true } +enum-iterator = { workspace = true } +strum = { workspace = true } +serde = { workspace = true } +hkdf = { workspace = true } +hex = { workspace = true } +zeroize = { workspace = true } +paste = { workspace = true } +k256 = { workspace = true } +radix-common = { workspace = true } # Hash +pretty_assertions = { workspace = true } +actix-rt = { workspace = true } +derive_more = { workspace = true } +enum-as-inner = { workspace = true } +serde_repr = { workspace = true } +serde_with = { workspace = true } +serde_json = { workspace = true } +aes-gcm = { workspace = true } +sargoncommon = { path = "../common" } +ret = { path = "../ret" } +hd = { path = "../hierarchical_deterministic" } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } + +[lib] +crate-type = ["staticlib", "cdylib", "lib"] diff --git a/crates/profile/build.rs b/crates/profile/build.rs new file mode 100644 index 000000000..977170151 --- /dev/null +++ b/crates/profile/build.rs @@ -0,0 +1,29 @@ +use std::path::Path; + +pub fn main() { + // Paths for reading fixtures used by tests + let fixtures_path = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../fixtures"); + println!("cargo:rustc-env=FIXTURES={}/", fixtures_path.display()); + + let fixtures_transaction_path = fixtures_path.join("transaction"); + println!( + "cargo:rustc-env=FIXTURES_TX={}/", + fixtures_transaction_path.display() + ); + + let fixtures_vector_path = fixtures_path.join("vector"); + println!( + "cargo:rustc-env=FIXTURES_VECTOR={}/", + fixtures_vector_path.display() + ); + + let fixtures_models_path = fixtures_path.join("models"); + println!( + "cargo:rustc-env=FIXTURES_MODELS={}/", + fixtures_models_path.display() + ); + + uniffi::generate_scaffolding("src/profile.udl") + .expect("Should be able to build."); +} diff --git a/src/hierarchical_deterministic/cap26/cap26_entity_kind.rs b/crates/profile/src/cap26_derivation/cap26/cap26_entity_kind.rs similarity index 100% rename from src/hierarchical_deterministic/cap26/cap26_entity_kind.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_entity_kind.rs diff --git a/src/hierarchical_deterministic/cap26/cap26_key_kind.rs b/crates/profile/src/cap26_derivation/cap26/cap26_key_kind.rs similarity index 100% rename from src/hierarchical_deterministic/cap26/cap26_key_kind.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_key_kind.rs diff --git a/src/hierarchical_deterministic/cap26/cap26_path/cap26_path.rs b/crates/profile/src/cap26_derivation/cap26/cap26_path/cap26_path.rs similarity index 98% rename from src/hierarchical_deterministic/cap26/cap26_path/cap26_path.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_path/cap26_path.rs index 7cd149633..671212125 100644 --- a/src/hierarchical_deterministic/cap26/cap26_path/cap26_path.rs +++ b/crates/profile/src/cap26_derivation/cap26/cap26_path/cap26_path.rs @@ -65,10 +65,8 @@ impl Derivation for CAP26Path { value: self.clone(), } } -} -impl CAP26Path { - pub fn scheme(&self) -> DerivationPathScheme { + fn scheme(&self) -> DerivationPathScheme { DerivationPathScheme::Cap26 } } diff --git a/src/hierarchical_deterministic/cap26/cap26_path/cap26_path_uniffi_fn.rs b/crates/profile/src/cap26_derivation/cap26/cap26_path/cap26_path_uniffi_fn.rs similarity index 100% rename from src/hierarchical_deterministic/cap26/cap26_path/cap26_path_uniffi_fn.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_path/cap26_path_uniffi_fn.rs diff --git a/src/hierarchical_deterministic/cap26/cap26_path/mod.rs b/crates/profile/src/cap26_derivation/cap26/cap26_path/mod.rs similarity index 100% rename from src/hierarchical_deterministic/cap26/cap26_path/mod.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_path/mod.rs diff --git a/src/hierarchical_deterministic/cap26/cap26_path/paths/account_path.rs b/crates/profile/src/cap26_derivation/cap26/cap26_path/paths/account_path.rs similarity index 96% rename from src/hierarchical_deterministic/cap26/cap26_path/paths/account_path.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_path/paths/account_path.rs index ce15ebb53..dcf72c26d 100644 --- a/src/hierarchical_deterministic/cap26/cap26_path/paths/account_path.rs +++ b/crates/profile/src/cap26_derivation/cap26/cap26_path/paths/account_path.rs @@ -117,10 +117,7 @@ impl Derivation for AccountPath { }, } } -} - -impl AccountPath { - pub fn scheme(&self) -> DerivationPathScheme { + fn scheme(&self) -> DerivationPathScheme { DerivationPathScheme::Cap26 } } @@ -214,8 +211,8 @@ mod tests { assert_eq!( AccountPath::from_str("m/44H/1022H/1H/618H/1460H/0H"), Err(CommonError::WrongEntityKind { - expected: CAP26EntityKind::Account, - found: CAP26EntityKind::Identity + expected: CAP26EntityKind::Account.discriminant(), + found: CAP26EntityKind::Identity.discriminant() }) ) } @@ -319,8 +316,8 @@ mod tests { assert_eq!( AccountPath::try_from(&hdpath), Err(CommonError::WrongEntityKind { - expected: CAP26EntityKind::Account, - found: CAP26EntityKind::Identity + expected: CAP26EntityKind::Account.discriminant(), + found: CAP26EntityKind::Identity.discriminant() }) ); } diff --git a/src/hierarchical_deterministic/cap26/cap26_path/paths/account_path_uniffi_fn.rs b/crates/profile/src/cap26_derivation/cap26/cap26_path/paths/account_path_uniffi_fn.rs similarity index 100% rename from src/hierarchical_deterministic/cap26/cap26_path/paths/account_path_uniffi_fn.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_path/paths/account_path_uniffi_fn.rs diff --git a/src/hierarchical_deterministic/cap26/cap26_path/paths/getid_path.rs b/crates/profile/src/cap26_derivation/cap26/cap26_path/paths/getid_path.rs similarity index 98% rename from src/hierarchical_deterministic/cap26/cap26_path/paths/getid_path.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_path/paths/getid_path.rs index 88e4032dc..bfcf356a8 100644 --- a/src/hierarchical_deterministic/cap26/cap26_path/paths/getid_path.rs +++ b/crates/profile/src/cap26_derivation/cap26/cap26_path/paths/getid_path.rs @@ -35,10 +35,8 @@ impl Derivation for GetIDPath { fn curve(&self) -> SLIP10Curve { self.scheme().curve() } -} -impl GetIDPath { - pub fn scheme(&self) -> DerivationPathScheme { + fn scheme(&self) -> DerivationPathScheme { DerivationPathScheme::Cap26 } } diff --git a/src/hierarchical_deterministic/cap26/cap26_path/paths/identity_path.rs b/crates/profile/src/cap26_derivation/cap26/cap26_path/paths/identity_path.rs similarity index 96% rename from src/hierarchical_deterministic/cap26/cap26_path/paths/identity_path.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_path/paths/identity_path.rs index d3be50470..2e58c349e 100644 --- a/src/hierarchical_deterministic/cap26/cap26_path/paths/identity_path.rs +++ b/crates/profile/src/cap26_derivation/cap26/cap26_path/paths/identity_path.rs @@ -117,10 +117,8 @@ impl Derivation for IdentityPath { fn curve(&self) -> SLIP10Curve { self.scheme().curve() } -} -impl IdentityPath { - pub fn scheme(&self) -> DerivationPathScheme { + fn scheme(&self) -> DerivationPathScheme { DerivationPathScheme::Cap26 } } @@ -223,8 +221,8 @@ mod tests { assert_eq!( IdentityPath::from_str("m/44H/1022H/1H/525H/1460H/0H"), Err(CommonError::WrongEntityKind { - expected: CAP26EntityKind::Identity, - found: CAP26EntityKind::Account, + expected: CAP26EntityKind::Identity.discriminant(), + found: CAP26EntityKind::Account.discriminant(), }) ) } @@ -338,8 +336,8 @@ mod tests { assert_eq!( IdentityPath::try_from(&hdpath), Err(CommonError::WrongEntityKind { - expected: CAP26EntityKind::Identity, - found: CAP26EntityKind::Account + expected: CAP26EntityKind::Identity.discriminant(), + found: CAP26EntityKind::Account.discriminant() }) ); } diff --git a/src/hierarchical_deterministic/cap26/cap26_path/paths/identity_path_uniffi_fn.rs b/crates/profile/src/cap26_derivation/cap26/cap26_path/paths/identity_path_uniffi_fn.rs similarity index 100% rename from src/hierarchical_deterministic/cap26/cap26_path/paths/identity_path_uniffi_fn.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_path/paths/identity_path_uniffi_fn.rs diff --git a/src/hierarchical_deterministic/cap26/cap26_path/paths/is_entity_path.rs b/crates/profile/src/cap26_derivation/cap26/cap26_path/paths/is_entity_path.rs similarity index 100% rename from src/hierarchical_deterministic/cap26/cap26_path/paths/is_entity_path.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_path/paths/is_entity_path.rs diff --git a/src/hierarchical_deterministic/cap26/cap26_path/paths/mod.rs b/crates/profile/src/cap26_derivation/cap26/cap26_path/paths/mod.rs similarity index 100% rename from src/hierarchical_deterministic/cap26/cap26_path/paths/mod.rs rename to crates/profile/src/cap26_derivation/cap26/cap26_path/paths/mod.rs diff --git a/src/hierarchical_deterministic/cap26/entity_cap26_path.rs b/crates/profile/src/cap26_derivation/cap26/entity_cap26_path.rs similarity index 97% rename from src/hierarchical_deterministic/cap26/entity_cap26_path.rs rename to crates/profile/src/cap26_derivation/cap26/entity_cap26_path.rs index 53af332d3..f5888a11b 100644 --- a/src/hierarchical_deterministic/cap26/entity_cap26_path.rs +++ b/crates/profile/src/cap26_derivation/cap26/entity_cap26_path.rs @@ -56,8 +56,8 @@ pub trait EntityCAP26Path: Derivation + FromStr { if entity_kind != Self::entity_kind() { return Err(CommonError::WrongEntityKind { - expected: Self::entity_kind(), - found: entity_kind, + expected: Self::entity_kind().discriminant(), + found: entity_kind.discriminant(), }); } diff --git a/src/hierarchical_deterministic/cap26/mod.rs b/crates/profile/src/cap26_derivation/cap26/mod.rs similarity index 100% rename from src/hierarchical_deterministic/cap26/mod.rs rename to crates/profile/src/cap26_derivation/cap26/mod.rs diff --git a/src/hierarchical_deterministic/derivation/derivation.rs b/crates/profile/src/cap26_derivation/derivation/derivation.rs similarity index 90% rename from src/hierarchical_deterministic/derivation/derivation.rs rename to crates/profile/src/cap26_derivation/derivation/derivation.rs index b1f5ac96e..4994d06b0 100644 --- a/src/hierarchical_deterministic/derivation/derivation.rs +++ b/crates/profile/src/cap26_derivation/derivation/derivation.rs @@ -1,5 +1,6 @@ use crate::prelude::*; pub trait Derivation: Sized { + fn scheme(&self) -> DerivationPathScheme; fn curve(&self) -> SLIP10Curve; fn derivation_path(&self) -> DerivationPath; fn hd_path(&self) -> &HDPath; diff --git a/src/hierarchical_deterministic/derivation/derivation_path.rs b/crates/profile/src/cap26_derivation/derivation/derivation_path.rs similarity index 99% rename from src/hierarchical_deterministic/derivation/derivation_path.rs rename to crates/profile/src/cap26_derivation/derivation/derivation_path.rs index 2c92d3849..c398247ad 100644 --- a/src/hierarchical_deterministic/derivation/derivation_path.rs +++ b/crates/profile/src/cap26_derivation/derivation/derivation_path.rs @@ -111,10 +111,8 @@ impl Derivation for DerivationPath { fn derivation_path(&self) -> DerivationPath { self.clone() } -} -impl DerivationPath { - pub fn scheme(&self) -> DerivationPathScheme { + fn scheme(&self) -> DerivationPathScheme { match self { DerivationPath::CAP26 { value: _ } => DerivationPathScheme::Cap26, DerivationPath::BIP44Like { value: _ } => { diff --git a/src/hierarchical_deterministic/derivation/derivation_path_scheme.rs b/crates/profile/src/cap26_derivation/derivation/derivation_path_scheme.rs similarity index 100% rename from src/hierarchical_deterministic/derivation/derivation_path_scheme.rs rename to crates/profile/src/cap26_derivation/derivation/derivation_path_scheme.rs diff --git a/src/hierarchical_deterministic/derivation/derivation_path_uniffi_fn.rs b/crates/profile/src/cap26_derivation/derivation/derivation_path_uniffi_fn.rs similarity index 100% rename from src/hierarchical_deterministic/derivation/derivation_path_uniffi_fn.rs rename to crates/profile/src/cap26_derivation/derivation/derivation_path_uniffi_fn.rs diff --git a/src/hierarchical_deterministic/derivation/hierarchical_deterministic_private_key.rs b/crates/profile/src/cap26_derivation/derivation/hierarchical_deterministic_private_key.rs similarity index 93% rename from src/hierarchical_deterministic/derivation/hierarchical_deterministic_private_key.rs rename to crates/profile/src/cap26_derivation/derivation/hierarchical_deterministic_private_key.rs index a1d16e179..0438a632e 100644 --- a/src/hierarchical_deterministic/derivation/hierarchical_deterministic_private_key.rs +++ b/crates/profile/src/cap26_derivation/derivation/hierarchical_deterministic_private_key.rs @@ -128,4 +128,12 @@ mod tests { let signature = sut.sign(&hash); assert!(signature.is_valid_for_hash(&hash)); } + + #[test] + fn is_valid() { + let private_key = HierarchicalDeterministicPrivateKey::sample(); + let msg = Hash::sample(); + let sut = private_key.sign(&msg); + assert!(signature_with_public_key_is_valid(&sut, &msg)); + } } diff --git a/src/hierarchical_deterministic/derivation/hierarchical_deterministic_public_key.rs b/crates/profile/src/cap26_derivation/derivation/hierarchical_deterministic_public_key.rs similarity index 100% rename from src/hierarchical_deterministic/derivation/hierarchical_deterministic_public_key.rs rename to crates/profile/src/cap26_derivation/derivation/hierarchical_deterministic_public_key.rs diff --git a/src/hierarchical_deterministic/derivation/hierarchical_deterministic_public_key_uniffi_fn.rs b/crates/profile/src/cap26_derivation/derivation/hierarchical_deterministic_public_key_uniffi_fn.rs similarity index 100% rename from src/hierarchical_deterministic/derivation/hierarchical_deterministic_public_key_uniffi_fn.rs rename to crates/profile/src/cap26_derivation/derivation/hierarchical_deterministic_public_key_uniffi_fn.rs diff --git a/src/hierarchical_deterministic/derivation/mnemonic_with_passphrase.rs b/crates/profile/src/cap26_derivation/derivation/mnemonic_with_passphrase.rs similarity index 97% rename from src/hierarchical_deterministic/derivation/mnemonic_with_passphrase.rs rename to crates/profile/src/cap26_derivation/derivation/mnemonic_with_passphrase.rs index cee29e6a2..7b429608b 100644 --- a/src/hierarchical_deterministic/derivation/mnemonic_with_passphrase.rs +++ b/crates/profile/src/cap26_derivation/derivation/mnemonic_with_passphrase.rs @@ -21,6 +21,7 @@ pub struct MnemonicWithPassphrase { pub mnemonic: Mnemonic, pub passphrase: BIP39Passphrase, } +json_data_convertible!(MnemonicWithPassphrase); impl MnemonicWithPassphrase { #[cfg(not(tarpaulin_include))] // false negative @@ -205,6 +206,14 @@ mod tests { assert!(sut.validate_public_keys(hd_keys)) } + #[test] + fn zeroize() { + let mut sut = MnemonicWithPassphrase::sample().to_seed(); + assert!(!sut.is_zeroized()); + sut.zeroize(); + assert!(sut.is_zeroized()); + } + #[test] fn sign() { let sut = SUT::sample(); @@ -364,7 +373,7 @@ mod tests { } #[test] - fn zeroize() { + fn test_zeroize() { let mut sut = SUT::sample(); sut.zeroize(); assert_ne!(sut, SUT::sample()); diff --git a/src/hierarchical_deterministic/derivation/mnemonic_with_passphrase_uniffi_fn.rs b/crates/profile/src/cap26_derivation/derivation/mnemonic_with_passphrase_uniffi_fn.rs similarity index 98% rename from src/hierarchical_deterministic/derivation/mnemonic_with_passphrase_uniffi_fn.rs rename to crates/profile/src/cap26_derivation/derivation/mnemonic_with_passphrase_uniffi_fn.rs index b4303b245..a8636e349 100644 --- a/src/hierarchical_deterministic/derivation/mnemonic_with_passphrase_uniffi_fn.rs +++ b/crates/profile/src/cap26_derivation/derivation/mnemonic_with_passphrase_uniffi_fn.rs @@ -1,7 +1,5 @@ use crate::prelude::*; -json_data_convertible!(MnemonicWithPassphrase); - #[uniffi::export] pub fn new_mnemonic_with_passphrase_sample() -> MnemonicWithPassphrase { MnemonicWithPassphrase::sample() diff --git a/src/hierarchical_deterministic/derivation/mod.rs b/crates/profile/src/cap26_derivation/derivation/mod.rs similarity index 100% rename from src/hierarchical_deterministic/derivation/mod.rs rename to crates/profile/src/cap26_derivation/derivation/mod.rs diff --git a/crates/profile/src/cap26_derivation/from_hd_factor_instance.rs b/crates/profile/src/cap26_derivation/from_hd_factor_instance.rs new file mode 100644 index 000000000..e4204eb98 --- /dev/null +++ b/crates/profile/src/cap26_derivation/from_hd_factor_instance.rs @@ -0,0 +1,87 @@ +use crate::prelude::*; + +pub trait FromHDFactorInstance: EntityAddress { + fn from_hd_factor_instance_virtual_entity_creation< + E: IsEntityPath + Clone, + >( + hd_factor_instance_virtual_entity_creation: HDFactorInstanceTransactionSigning, + ) -> Self { + let network_id = + hd_factor_instance_virtual_entity_creation.path.network_id(); + + Self::from_public_key( + hd_factor_instance_virtual_entity_creation + .public_key() + .public_key, + network_id, + ) + } +} +impl FromHDFactorInstance for AccountAddress {} +impl FromHDFactorInstance for IdentityAddress {} + +pub trait FromAppearanceID { + fn new( + account_creating_factor_instance: HDFactorInstanceAccountCreation, + display_name: DisplayName, + appearance_id: AppearanceID, + ) -> Self; +} + +impl FromAppearanceID for Account { + fn new( + account_creating_factor_instance: HDFactorInstanceAccountCreation, + display_name: DisplayName, + appearance_id: AppearanceID, + ) -> Self { + let address = + AccountAddress::from_hd_factor_instance_virtual_entity_creation( + account_creating_factor_instance.clone(), + ); + Self { + network_id: account_creating_factor_instance.network_id(), + address, + display_name, + security_state: + UnsecuredEntityControl::with_entity_creating_factor_instance( + account_creating_factor_instance, + ) + .into(), + appearance_id, + flags: EntityFlags::default(), + on_ledger_settings: OnLedgerSettings::default(), + } + } +} +pub trait FromPersonaData { + fn new( + persona_creating_factor_instance: HDFactorInstanceIdentityCreation, + display_name: DisplayName, + persona_data: impl Into>, + ) -> Self; +} +impl FromPersonaData for Persona { + /// Creates a new `Persona`, if `persona_data` is `None`, an empty object will be created. + fn new( + persona_creating_factor_instance: HDFactorInstanceIdentityCreation, + display_name: DisplayName, + persona_data: impl Into>, + ) -> Self { + let address = + IdentityAddress::from_hd_factor_instance_virtual_entity_creation( + persona_creating_factor_instance.clone(), + ); + Self { + network_id: persona_creating_factor_instance.network_id(), + address, + display_name, + security_state: + UnsecuredEntityControl::with_entity_creating_factor_instance( + persona_creating_factor_instance, + ) + .into(), + flags: EntityFlags::default(), + persona_data: persona_data.into().unwrap_or_default(), + } + } +} diff --git a/crates/profile/src/cap26_derivation/impl_traits.rs b/crates/profile/src/cap26_derivation/impl_traits.rs new file mode 100644 index 000000000..cca1d2af8 --- /dev/null +++ b/crates/profile/src/cap26_derivation/impl_traits.rs @@ -0,0 +1,76 @@ +use crate::prelude::*; + +impl Derivation for BIP44LikePath { + fn curve(&self) -> SLIP10Curve { + self.scheme().curve() + } + + fn derivation_path(&self) -> DerivationPath { + DerivationPath::BIP44Like { + value: self.clone(), + } + } + fn hd_path(&self) -> &HDPath { + &self.path + } + + fn scheme(&self) -> DerivationPathScheme { + DerivationPathScheme::Bip44Olympia + } +} + +#[uniffi::export] +pub fn bip44_like_path_get_address_index(path: &BIP44LikePath) -> HDPathValue { + path.last_component().index() +} + +pub trait HDPrivateKeyDeriving { + fn derive_private_key( + &self, + derivation: &D, + ) -> HierarchicalDeterministicPrivateKey + where + D: Derivation; +} + +impl HDPrivateKeyDeriving for BIP39Seed { + fn derive_private_key( + &self, + derivation: &D, + ) -> HierarchicalDeterministicPrivateKey + where + D: Derivation, + { + match derivation.curve() { + SLIP10Curve::Curve25519 => { + let key = self.derive_ed25519_private_key(derivation.hd_path()); + HierarchicalDeterministicPrivateKey::new( + key.into(), + derivation.derivation_path(), + ) + } + SLIP10Curve::Secp256k1 => { + let key = + self.derive_secp256k1_private_key(derivation.hd_path()); + HierarchicalDeterministicPrivateKey::new( + key.into(), + derivation.derivation_path(), + ) + } + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_bip44_like_path_get_address_index() { + assert_eq!( + bip44_like_path_get_address_index(&BIP44LikePath::sample_other()), + 1 + ) + } +} diff --git a/crates/profile/src/cap26_derivation/mod.rs b/crates/profile/src/cap26_derivation/mod.rs new file mode 100644 index 000000000..838427b4a --- /dev/null +++ b/crates/profile/src/cap26_derivation/mod.rs @@ -0,0 +1,9 @@ +mod cap26; +mod derivation; +mod from_hd_factor_instance; +mod impl_traits; + +pub use cap26::*; +pub use derivation::*; +pub use from_hd_factor_instance::*; +pub use impl_traits::*; diff --git a/src/profile/encrypted/encryption/aes_gcm_256.rs b/crates/profile/src/encrypted/encryption/aes_gcm_256.rs similarity index 92% rename from src/profile/encrypted/encryption/aes_gcm_256.rs rename to crates/profile/src/encrypted/encryption/aes_gcm_256.rs index 9e66b152a..b2e2b7529 100644 --- a/src/profile/encrypted/encryption/aes_gcm_256.rs +++ b/crates/profile/src/encrypted/encryption/aes_gcm_256.rs @@ -1,8 +1,8 @@ use crate::prelude::*; use aes_gcm::{ - aead::{generic_array::sequence::Concat, Aead, AeadCore, KeyInit, OsRng}, - Aes256Gcm, Key, Nonce, + aead::{Aead, AeadCore, KeyInit, OsRng}, + Key, }; /// AES GCM 256 encryption @@ -94,12 +94,6 @@ impl VersionedEncryption for AesGcm256 { } } -impl From for Key { - fn from(value: Exactly32Bytes) -> Self { - Self::from(*value.bytes()) - } -} - #[cfg(test)] mod tests { diff --git a/src/profile/encrypted/encryption/aes_gcm_sealed_box.rs b/crates/profile/src/encrypted/encryption/aes_gcm_sealed_box.rs similarity index 100% rename from src/profile/encrypted/encryption/aes_gcm_sealed_box.rs rename to crates/profile/src/encrypted/encryption/aes_gcm_sealed_box.rs diff --git a/src/profile/encrypted/encryption/encrypted_profile_snapshot.rs b/crates/profile/src/encrypted/encryption/encrypted_profile_snapshot.rs similarity index 100% rename from src/profile/encrypted/encryption/encrypted_profile_snapshot.rs rename to crates/profile/src/encrypted/encryption/encrypted_profile_snapshot.rs diff --git a/src/profile/encrypted/encryption/encryption_scheme.rs b/crates/profile/src/encrypted/encryption/encryption_scheme.rs similarity index 100% rename from src/profile/encrypted/encryption/encryption_scheme.rs rename to crates/profile/src/encrypted/encryption/encryption_scheme.rs diff --git a/src/profile/encrypted/encryption/encryption_scheme_version.rs b/crates/profile/src/encrypted/encryption/encryption_scheme_version.rs similarity index 100% rename from src/profile/encrypted/encryption/encryption_scheme_version.rs rename to crates/profile/src/encrypted/encryption/encryption_scheme_version.rs diff --git a/src/profile/encrypted/encryption/mod.rs b/crates/profile/src/encrypted/encryption/mod.rs similarity index 100% rename from src/profile/encrypted/encryption/mod.rs rename to crates/profile/src/encrypted/encryption/mod.rs diff --git a/src/profile/encrypted/encryption/versioned_encryption.rs b/crates/profile/src/encrypted/encryption/versioned_encryption.rs similarity index 100% rename from src/profile/encrypted/encryption/versioned_encryption.rs rename to crates/profile/src/encrypted/encryption/versioned_encryption.rs diff --git a/src/profile/encrypted/key_derivation/mod.rs b/crates/profile/src/encrypted/key_derivation/mod.rs similarity index 100% rename from src/profile/encrypted/key_derivation/mod.rs rename to crates/profile/src/encrypted/key_derivation/mod.rs diff --git a/src/profile/encrypted/key_derivation/password_based_key_derivation_scheme.rs b/crates/profile/src/encrypted/key_derivation/password_based_key_derivation_scheme.rs similarity index 100% rename from src/profile/encrypted/key_derivation/password_based_key_derivation_scheme.rs rename to crates/profile/src/encrypted/key_derivation/password_based_key_derivation_scheme.rs diff --git a/src/profile/encrypted/key_derivation/password_based_key_derivation_scheme_version.rs b/crates/profile/src/encrypted/key_derivation/password_based_key_derivation_scheme_version.rs similarity index 100% rename from src/profile/encrypted/key_derivation/password_based_key_derivation_scheme_version.rs rename to crates/profile/src/encrypted/key_derivation/password_based_key_derivation_scheme_version.rs diff --git a/src/profile/encrypted/key_derivation/pb_hkdf_sha256.rs b/crates/profile/src/encrypted/key_derivation/pb_hkdf_sha256.rs similarity index 100% rename from src/profile/encrypted/key_derivation/pb_hkdf_sha256.rs rename to crates/profile/src/encrypted/key_derivation/pb_hkdf_sha256.rs diff --git a/src/profile/encrypted/key_derivation/versioned_password_based_key_derivation.rs b/crates/profile/src/encrypted/key_derivation/versioned_password_based_key_derivation.rs similarity index 100% rename from src/profile/encrypted/key_derivation/versioned_password_based_key_derivation.rs rename to crates/profile/src/encrypted/key_derivation/versioned_password_based_key_derivation.rs diff --git a/src/profile/encrypted/mod.rs b/crates/profile/src/encrypted/mod.rs similarity index 100% rename from src/profile/encrypted/mod.rs rename to crates/profile/src/encrypted/mod.rs diff --git a/src/profile/encrypted/version_of_algorithm.rs b/crates/profile/src/encrypted/version_of_algorithm.rs similarity index 96% rename from src/profile/encrypted/version_of_algorithm.rs rename to crates/profile/src/encrypted/version_of_algorithm.rs index 032ac44ad..b28f0b4ac 100644 --- a/src/profile/encrypted/version_of_algorithm.rs +++ b/crates/profile/src/encrypted/version_of_algorithm.rs @@ -1,5 +1,3 @@ -use crate::prelude::*; - /// A version of an algorithm so that we can change the implementation between /// app releases and remain backwards compatible. pub trait VersionOfAlgorithm { diff --git a/src/profile/encrypted/versioned_algorithm.rs b/crates/profile/src/encrypted/versioned_algorithm.rs similarity index 100% rename from src/profile/encrypted/versioned_algorithm.rs rename to crates/profile/src/encrypted/versioned_algorithm.rs diff --git a/crates/profile/src/lib.rs b/crates/profile/src/lib.rs new file mode 100644 index 000000000..d8d04021c --- /dev/null +++ b/crates/profile/src/lib.rs @@ -0,0 +1,30 @@ +#![feature(async_closure)] +#![feature(let_chains)] + +mod cap26_derivation; +mod encrypted; +mod logic; +mod profilesnapshot_version; +mod supporting_types; +mod v100; + +uniffi::remote_type!(Uuid, sargoncommon); +uniffi::remote_type!(Timestamp, sargoncommon); +uniffi::remote_type!(Url, sargoncommon); + +pub mod prelude { + + pub use crate::cap26_derivation::*; + pub use crate::encrypted::*; + pub use crate::logic::*; + pub use crate::profilesnapshot_version::*; + pub use crate::supporting_types::*; + pub use crate::v100::*; + + pub use hd::prelude::*; + pub use ret::prelude::*; +} + +pub use prelude::*; + +uniffi::include_scaffolding!("profile"); diff --git a/src/profile/logic/account/accounts_visibility.rs b/crates/profile/src/logic/account/accounts_visibility.rs similarity index 100% rename from src/profile/logic/account/accounts_visibility.rs rename to crates/profile/src/logic/account/accounts_visibility.rs diff --git a/crates/profile/src/logic/account/create_account.rs b/crates/profile/src/logic/account/create_account.rs new file mode 100644 index 000000000..047d63d2e --- /dev/null +++ b/crates/profile/src/logic/account/create_account.rs @@ -0,0 +1,139 @@ +use crate::prelude::*; +use std::future::Future; + +impl Profile { + /// Creates a new non securified account **WITHOUT** add it to Profile, using the *main* "Babylon" + /// `DeviceFactorSource` and the "next" index for this FactorSource as derivation path. + /// + /// If you want to add it to Profile, call `add_account(account)` + pub async fn create_unsaved_account( + &self, + network_id: NetworkID, + name: DisplayName, + load_private_device_factor_source: F, + ) -> Result + where + F: FnOnce(DeviceFactorSource) -> Fut, + Fut: Future< + Output = Result, + >, + { + let accounts = self + .create_unsaved_accounts( + network_id, + 1, + |_| name.clone(), + load_private_device_factor_source, + ) + .await?; + + let account = accounts + .iter() + .clone() + .last() + .expect("Should have created one account"); + + Ok(account) + } + + /// Creates many new non securified accounts **WITHOUT** add them to Profile, using the *main* "Babylon" + /// `DeviceFactorSource` and the "next" index for this FactorSource as derivation paths. + /// + /// If you want to add the accounts to Profile, call `add_accounts(accounts)` + pub async fn create_unsaved_accounts( + &self, + network_id: NetworkID, + count: u16, + get_name: impl Fn(u32) -> DisplayName, // name of account at index + load_private_device_factor_source: F, + ) -> Result + where + F: FnOnce(DeviceFactorSource) -> Fut, + Fut: Future< + Output = Result, + >, + { + let index = self + .next_derivation_index_for_entity(EntityKind::Account, network_id); + + assert!((index as i64) - (count as i64) < (u32::MAX as i64)); // unlikely edge case + + let bdfs = self.bdfs(); + let count = count as u32; + + let number_of_accounts_on_network = self + .networks + .get_id(network_id) + .map(|n| n.accounts.len()) + .unwrap_or(0); + + let indices = index..index + count; + + let factor_instances = load_private_device_factor_source(bdfs.clone()) + .await + .map(|p| { + assert_eq!(p.factor_source, bdfs); + p.derive_entity_creation_factor_instances(network_id, indices) + })?; + + let accounts = factor_instances + .iter() + .clone() + .map(|f| { + let idx = f.index(); + let name = get_name(idx); + let appearance_id = + AppearanceID::from_number_of_accounts_on_network( + (idx as usize) + number_of_accounts_on_network, + ); + + Account::new(f.clone(), name, appearance_id) + }) + .collect::(); + + Ok(accounts) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[actix_rt::test] + async fn test_create_unsaved_accounts() { + let sut = Profile::from_device_factor_source( + DeviceFactorSource::sample(), + DeviceInfo::sample(), + ); + + let accounts = sut + .create_unsaved_accounts( + NetworkID::Mainnet, + 3, + |i| { + DisplayName::new(if i == 0 { + "Alice" + } else if i == 1 { + "Bob" + } else { + "Carol" + }) + .unwrap() + }, + async move |_| { + Ok(PrivateHierarchicalDeterministicFactorSource::sample()) + }, + ) + .await + .unwrap(); + + pretty_assertions::assert_eq!( + accounts, + Accounts::from_iter([ + Account::sample_mainnet_alice(), + Account::sample_mainnet_bob(), + Account::sample_mainnet_carol() + ]) + ) + } +} diff --git a/crates/profile/src/logic/account/mod.rs b/crates/profile/src/logic/account/mod.rs new file mode 100644 index 000000000..259774f3c --- /dev/null +++ b/crates/profile/src/logic/account/mod.rs @@ -0,0 +1,3 @@ +mod accounts_visibility; +mod create_account; +mod query_accounts; diff --git a/crates/profile/src/logic/account/query_accounts.rs b/crates/profile/src/logic/account/query_accounts.rs new file mode 100644 index 000000000..56640e284 --- /dev/null +++ b/crates/profile/src/logic/account/query_accounts.rs @@ -0,0 +1,81 @@ +use crate::prelude::*; + +impl Profile { + /// Returns the non-hidden accounts on the current network, empty if no accounts + /// on the network + pub fn accounts_on_current_network(&self) -> Accounts { + self.current_network().accounts.non_hidden() + } + + /// Returns the non-hidden accounts on the current network as `AccountForDisplay` + pub fn accounts_for_display_on_current_network( + &self, + ) -> AccountsForDisplay { + self.accounts_on_current_network() + .iter() + .map(AccountForDisplay::from) + .collect::() + } + + /// Looks up the account by account address, returns Err if the account is + /// unknown, will return a hidden account if queried for. + pub fn account_by_address( + &self, + address: AccountAddress, + ) -> Result { + for network in self.networks.iter() { + if let Some(account) = network.accounts.get_id(address) { + return Ok(account.clone()); + } + } + Err(CommonError::UnknownAccount) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Profile; + + #[test] + fn test_accounts_on_current_network() { + let sut = SUT::sample(); + assert_eq!( + sut.accounts_on_current_network(), + Accounts::sample_mainnet() + ); + } + + #[test] + fn test_accounts_on_current_network_stokenet() { + let sut = SUT::sample_other(); + assert_eq!( + sut.accounts_on_current_network(), + Accounts::just(Account::sample_stokenet_nadia()) // olivia is hidden + ); + } + + #[test] + fn test_accounts_for_display_on_current_network() { + let sut = SUT::sample(); + assert_eq!( + sut.accounts_for_display_on_current_network(), + Accounts::sample_mainnet() + .iter() + .map(AccountForDisplay::from) + .collect::() + ); + } + + #[test] + fn test_account_by_address() { + let sut = SUT::sample(); + assert_eq!( + sut.account_by_address(Account::sample_mainnet().address), + Ok(Account::sample_mainnet()) + ); + } +} diff --git a/crates/profile/src/logic/gateway/current_gateway.rs b/crates/profile/src/logic/gateway/current_gateway.rs new file mode 100644 index 000000000..3d94af29e --- /dev/null +++ b/crates/profile/src/logic/gateway/current_gateway.rs @@ -0,0 +1,139 @@ +use crate::prelude::*; + +impl Profile { + /// Returns the `current` gateway in AppPreferences, used by host clients to + /// know the NetworkID currently being used. + pub fn current_gateway(&self) -> Gateway { + self.app_preferences.gateways.current.clone() + } + + /// The NetworkID currently being used, dependent on `current` gateway in + /// AppPreferences + pub fn current_network_id(&self) -> NetworkID { + self.current_gateway().network.id + } + + /// The ProfileNetwork of the currently used Network dependent on the `current` + /// Gateway set in AppPreferences. This affects which Accounts users see in + /// "Home screen" in wallet apps. + pub fn current_network(&self) -> &ProfileNetwork { + self.networks + .get_id(self.current_network_id()) + .expect("Should have current network") + } +} + +/// When user changes `current` Gateway in AppPreferences host clients should +/// make it so that they can only change to non current gateway, this small type +/// represents the outcome of switching, e.g. if they just switched to a "new" +/// network, i.e. if the gateway was in `other` list in saved gateways, or if +/// we just added it. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, uniffi::Enum)] +pub enum ChangeGatewayOutcome { + /// If the we did in fact change the gateway, and if the gateway was unknown + /// or known before it was added, i.e. `is_new` will be true iff the gateway + /// was unknown before changing to it. + DidChange { + /// If the Gateway we just switched to already was in the `other` list of + /// saved gateways in AppPreferences, or if it was entirely new. + is_new: bool, + }, + + /// We tried to change to the current gateway. + NoChange, +} + +impl SavedGateways { + /// Changes the current Gateway to `to`, if it is not already the current. If `to` is + /// not a new Gateway, it will be removed from. Returns `Ok(false)` if `to` was already + /// the `current`, returns `Ok(true)` if `to` was not already `current`. + pub fn change_current(&mut self, to: Gateway) -> ChangeGatewayOutcome { + if self.current == to { + return ChangeGatewayOutcome::NoChange; + } + let old_current = &self.current; + let was_inserted = self.append_to_other(old_current.clone(), true); + if !was_inserted { + let msg = "Discrepancy! 'other' already contained 'current'"; + error!("{}", msg); + panic!("{}", msg); + } + let is_new = self.other.remove_id(&to.id()).is_none(); + self.current = to; + ChangeGatewayOutcome::DidChange { is_new } + } + + /// Appends `gateway` to the `other` list if `gateway` not equals `current`, + /// without changing the `current` Gateway. + /// If `other` already contains `gateway` then `(false, other.len())` is returned. + /// If `other` was new then `(true, index_of_new)` is returned. + /// + /// - Returns: `true` if it was added, `false` if it was already present (noop) + /// Appends `gateway` to the `other` list if `gateway` not equals `current`, + /// without changing the `current` Gateway. + /// If `other` already contains `gateway` then `(false, other.len())` is returned. + /// If `other` was new then `(true, index_of_new)` is returned. + /// + /// - Returns: `true` if it was added, `false` if it was already present (noop) + pub fn append(&mut self, gateway: Gateway) -> bool { + self.append_to_other(gateway, false) + } + + fn append_to_other( + &mut self, + gateway: Gateway, + is_switching: bool, + ) -> bool { + if !is_switching && self.current == gateway { + return false; + } + self.other.append(gateway).0 + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = SavedGateways; + + #[test] + fn change_current_to_new() { + let mut sut = SUT::default(); + assert_eq!(sut.current.network.id, NetworkID::Mainnet); + assert_eq!( + sut.change_current(Gateway::nebunet()), + ChangeGatewayOutcome::DidChange { is_new: true } + ); + assert_eq!(sut.current.network.id, NetworkID::Nebunet); + assert_eq!( + sut.other.items(), + [Gateway::stokenet(), Gateway::mainnet()] + ); + } + + #[test] + fn change_current_to_existing() { + let mut sut = SUT::default(); + assert_eq!(sut.current.network.id, NetworkID::Mainnet); + assert_eq!( + sut.change_current(Gateway::stokenet()), + ChangeGatewayOutcome::DidChange { is_new: false } + ); + assert_eq!(sut.current.network.id, NetworkID::Stokenet); + } + + #[test] + #[should_panic( + expected = "Discrepancy! 'other' already contained 'current'" + )] + fn change_throw_gateways_discrepancy_other_should_not_contain_current() { + let mut impossible = SUT { + current: Gateway::mainnet(), + other: Gateways::from_iter([Gateway::mainnet()]), + }; + let _ = impossible.change_current(Gateway::stokenet()); + } +} diff --git a/crates/profile/src/logic/gateway/mod.rs b/crates/profile/src/logic/gateway/mod.rs new file mode 100644 index 000000000..9dd21f69c --- /dev/null +++ b/crates/profile/src/logic/gateway/mod.rs @@ -0,0 +1,3 @@ +mod current_gateway; + +pub use current_gateway::*; diff --git a/src/profile/logic/mod.rs b/crates/profile/src/logic/mod.rs similarity index 58% rename from src/profile/logic/mod.rs rename to crates/profile/src/logic/mod.rs index 1c973855b..a12b4660b 100644 --- a/src/profile/logic/mod.rs +++ b/crates/profile/src/logic/mod.rs @@ -1,9 +1,11 @@ mod account; +mod gateway; mod persona; +mod profile_header; mod profile_network; +mod profile_networks; mod profile_next_derivation; -pub use account::*; -pub use persona::*; +pub use gateway::*; + pub use profile_network::*; -pub use profile_next_derivation::*; diff --git a/crates/profile/src/logic/persona/mod.rs b/crates/profile/src/logic/persona/mod.rs new file mode 100644 index 000000000..c6e79e8ff --- /dev/null +++ b/crates/profile/src/logic/persona/mod.rs @@ -0,0 +1,3 @@ +mod persona_data_ids; +mod personas_visibility; +mod shared_persona_data_ids; diff --git a/src/profile/logic/persona/persona_data_ids.rs b/crates/profile/src/logic/persona/persona_data_ids.rs similarity index 100% rename from src/profile/logic/persona/persona_data_ids.rs rename to crates/profile/src/logic/persona/persona_data_ids.rs diff --git a/src/profile/logic/persona/personas_visibility.rs b/crates/profile/src/logic/persona/personas_visibility.rs similarity index 100% rename from src/profile/logic/persona/personas_visibility.rs rename to crates/profile/src/logic/persona/personas_visibility.rs diff --git a/src/profile/logic/persona/shared_persona_data_ids.rs b/crates/profile/src/logic/persona/shared_persona_data_ids.rs similarity index 100% rename from src/profile/logic/persona/shared_persona_data_ids.rs rename to crates/profile/src/logic/persona/shared_persona_data_ids.rs diff --git a/crates/profile/src/logic/profile_header.rs b/crates/profile/src/logic/profile_header.rs new file mode 100644 index 000000000..755104dd2 --- /dev/null +++ b/crates/profile/src/logic/profile_header.rs @@ -0,0 +1,29 @@ +use crate::prelude::*; + +impl Header { + /// Updates `last_modified`, `content_hint` and also `last_used_on_device` if + /// it was specified. + pub fn update( + &mut self, + content_hint: ContentHint, + maybe_device_info: impl Into>, + ) { + if let Some(device_info) = maybe_device_info.into() { + self.last_used_on_device = device_info; + } + self.content_hint = content_hint; + self.last_modified = now(); + } +} + +impl Profile { + /// Updates the header's fields: `last_modified`, `content_hint` and also + /// `last_used_on_device` if it was specified. + pub fn update_header( + &mut self, + maybe_device_info: impl Into>, + ) { + let content_hint = self.networks.content_hint(); + self.header.update(content_hint, maybe_device_info) + } +} diff --git a/src/profile/logic/profile_network/mod.rs b/crates/profile/src/logic/profile_network/mod.rs similarity index 65% rename from src/profile/logic/profile_network/mod.rs rename to crates/profile/src/logic/profile_network/mod.rs index 862523f15..69a7d7720 100644 --- a/src/profile/logic/profile_network/mod.rs +++ b/crates/profile/src/logic/profile_network/mod.rs @@ -2,6 +2,4 @@ mod profile_network_details; mod profile_network_details_uniffi_fn; mod profile_network_get_entities; -pub use profile_network_details::*; pub use profile_network_details_uniffi_fn::*; -pub use profile_network_get_entities::*; diff --git a/src/profile/logic/profile_network/profile_network_details.rs b/crates/profile/src/logic/profile_network/profile_network_details.rs similarity index 96% rename from src/profile/logic/profile_network/profile_network_details.rs rename to crates/profile/src/logic/profile_network/profile_network_details.rs index 8d5072d3c..ba8c45d87 100644 --- a/src/profile/logic/profile_network/profile_network_details.rs +++ b/crates/profile/src/logic/profile_network/profile_network_details.rs @@ -17,7 +17,7 @@ impl AuthorizedPersonaSimple { // This is a sign that Profile is in a bad state somehow... warn!("Discrepancy! AuthorizedDapp references account which does not exist {}", account_address); return Err(CommonError::DiscrepancyAuthorizedDappReferencedAccountWhichDoesNotExist { - address: account_address.to_owned() + address: account_address.to_string() }) }; Ok(AccountForDisplay::new( @@ -101,7 +101,7 @@ impl AuthorizedPersonaSimple { // This is a sign that Profile is in a bad state somehow... warn!("Discrepancy! AuthorizedDapp references persona which does not exist {}", self.identity_address); return Err(CommonError::DiscrepancyAuthorizedDappReferencedPersonaWhichDoesNotExist { - address: self.identity_address + address: self.identity_address.to_string() }); }; Ok(persona.clone()) @@ -176,8 +176,8 @@ mod tests { assert_eq!( sut.details_for_authorized_dapp(&AuthorizedDapp::sample_stokenet()), Err(CommonError::NetworkDiscrepancy { - expected: sut.network_id(), - actual: NetworkID::Stokenet + expected: sut.network_id().discriminant(), + actual: NetworkID::Stokenet.discriminant() }) ) } @@ -193,7 +193,7 @@ mod tests { assert_eq!( sut.details_for_authorized_dapp(&dapp), - Err(CommonError::DiscrepancyAuthorizedDappReferencedPersonaWhichDoesNotExist { address: persona_simple.identity_address }) + Err(CommonError::DiscrepancyAuthorizedDappReferencedPersonaWhichDoesNotExist { address: persona_simple.identity_address.to_string() }) ); } @@ -219,7 +219,7 @@ mod tests { assert_eq!( sut.details_for_authorized_dapp(&dapp), - Err(CommonError::DiscrepancyAuthorizedDappReferencedAccountWhichDoesNotExist { address: address_of_non_existing_account }) + Err(CommonError::DiscrepancyAuthorizedDappReferencedAccountWhichDoesNotExist { address: address_of_non_existing_account.to_string() }) ); } diff --git a/src/profile/logic/profile_network/profile_network_details_uniffi_fn.rs b/crates/profile/src/logic/profile_network/profile_network_details_uniffi_fn.rs similarity index 100% rename from src/profile/logic/profile_network/profile_network_details_uniffi_fn.rs rename to crates/profile/src/logic/profile_network/profile_network_details_uniffi_fn.rs diff --git a/src/profile/logic/profile_network/profile_network_get_entities.rs b/crates/profile/src/logic/profile_network/profile_network_get_entities.rs similarity index 100% rename from src/profile/logic/profile_network/profile_network_get_entities.rs rename to crates/profile/src/logic/profile_network/profile_network_get_entities.rs diff --git a/crates/profile/src/logic/profile_networks.rs b/crates/profile/src/logic/profile_networks.rs new file mode 100644 index 000000000..21acc626d --- /dev/null +++ b/crates/profile/src/logic/profile_networks.rs @@ -0,0 +1,34 @@ +use crate::prelude::*; + +impl Profile { + /// If the user has **any** accounts on any network at all, including hidden + /// accounts. This can be used by host devices to prompt user to create their + /// first account or not, e.g. if user starts app after fresh install, the + /// SargonOS will create an "empty" Profile and BDFS and save it, before user + /// has had the chance to create their first account. If the user force quits + /// the app and then restart it, the app can still prompt user to create their + /// first account - as if no force-restart happened. + pub fn has_any_account_on_any_network(&self) -> bool { + self.networks.iter().any(|n| !n.accounts.is_empty()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Profile; + + #[test] + fn test_empty_profile_has_any_account_on_any_network_is_false() { + let sut = SUT::new(Mnemonic::sample(), DeviceInfo::sample()); + assert!(!sut.has_any_account_on_any_network()); + } + + #[test] + fn test_sample_profile_has_any_account_on_any_network() { + assert!(SUT::sample().has_any_account_on_any_network()); + assert!(SUT::sample_other().has_any_account_on_any_network()); + } +} diff --git a/src/profile/logic/profile_next_derivation.rs b/crates/profile/src/logic/profile_next_derivation.rs similarity index 92% rename from src/profile/logic/profile_next_derivation.rs rename to crates/profile/src/logic/profile_next_derivation.rs index 2c183aaf0..69bfcaf97 100644 --- a/src/profile/logic/profile_next_derivation.rs +++ b/crates/profile/src/logic/profile_next_derivation.rs @@ -13,13 +13,13 @@ impl Profile { self.factor_sources .get_id(&id) .ok_or(CommonError::ProfileDoesNotContainFactorSourceWithID { - bad_value: id.clone(), + bad_value: id.to_string(), }) .and_then(|f| { f.clone().try_into().map_err(|_| { CommonError::CastFactorSourceWrongKind { - expected: ::kind(), - found: f.factor_source_kind(), + expected: ::kind().to_string(), + found: f.factor_source_kind().to_string(), } }) }) @@ -32,14 +32,16 @@ impl Profile { self.factor_source_by_id(*id) } - pub fn bdfs(&self) -> DeviceFactorSource { - let device_factor_source = self - .factor_sources + pub fn device_factor_sources(&self) -> Vec { + self.factor_sources .iter() .filter_map(|f| f.as_device().cloned()) - .collect_vec(); + .collect_vec() + } - let explicit_main = device_factor_source + pub fn bdfs(&self) -> DeviceFactorSource { + let device_factor_sources = self.device_factor_sources(); + let explicit_main = device_factor_sources .clone() .into_iter() .filter(|x| x.is_main_bdfs()) @@ -47,7 +49,7 @@ impl Profile { .first() .cloned(); - let implicit_main = device_factor_source + let implicit_main = device_factor_sources .into_iter() .filter(|x| x.common.supports_babylon()) .collect_vec() @@ -74,7 +76,7 @@ impl Profile { }; let index = self .networks - .get_id(&network_id) + .get_id(network_id) .map(|n| { n.accounts .items() @@ -197,8 +199,8 @@ mod tests { dfs.factor_source_id() ), Err(CommonError::CastFactorSourceWrongKind { - expected: FactorSourceKind::LedgerHQHardwareWallet, - found: FactorSourceKind::Device, + expected: FactorSourceKind::LedgerHQHardwareWallet.to_string(), + found: FactorSourceKind::Device.to_string(), }) ); } @@ -212,7 +214,7 @@ mod tests { lfs.factor_source_id() ), Err(CommonError::ProfileDoesNotContainFactorSourceWithID { - bad_value: lfs.factor_source_id() + bad_value: lfs.factor_source_id().to_string() }) ); } @@ -235,7 +237,7 @@ mod tests { assert_eq!( profile.device_factor_source_by_id(&id), Err(CommonError::ProfileDoesNotContainFactorSourceWithID { - bad_value: id.into() + bad_value: id.to_string() }) ); } diff --git a/crates/profile/src/profile.udl b/crates/profile/src/profile.udl new file mode 100644 index 000000000..22a81679d --- /dev/null +++ b/crates/profile/src/profile.udl @@ -0,0 +1 @@ +namespace profile {}; diff --git a/src/profile/profilesnapshot_version.rs b/crates/profile/src/profilesnapshot_version.rs similarity index 100% rename from src/profile/profilesnapshot_version.rs rename to crates/profile/src/profilesnapshot_version.rs diff --git a/src/profile/supporting_types/account_for_display.rs b/crates/profile/src/supporting_types/account_for_display.rs similarity index 71% rename from src/profile/supporting_types/account_for_display.rs rename to crates/profile/src/supporting_types/account_for_display.rs index e2be18f40..6f7b3f7a8 100644 --- a/src/profile/supporting_types/account_for_display.rs +++ b/crates/profile/src/supporting_types/account_for_display.rs @@ -13,10 +13,12 @@ use crate::prelude::*; derive_more::Display, uniffi::Record, )] -#[display("{label} | {address}")] +#[display("{display_name} | {address}")] pub struct AccountForDisplay { pub address: AccountAddress, - pub label: DisplayName, + + #[serde(rename = "label")] + pub display_name: DisplayName, #[serde(rename = "appearanceID")] pub appearance_id: AppearanceID, @@ -25,12 +27,12 @@ pub struct AccountForDisplay { impl AccountForDisplay { pub fn new( address: impl Into, - label: impl Into, + display_name: impl Into, appearance_id: impl Into, ) -> Self { Self { address: address.into(), - label: label.into(), + display_name: display_name.into(), appearance_id: appearance_id.into(), } } @@ -54,6 +56,12 @@ impl HasSampleValues for AccountForDisplay { } } +impl From for AccountForDisplay { + fn from(value: Account) -> Self { + Self::new(value.address, value.display_name, value.appearance_id) + } +} + impl Identifiable for AccountForDisplay { type ID = AccountAddress; @@ -90,4 +98,17 @@ mod tests { fn test_is_network_aware() { assert_eq!(SUT::sample().network_id(), NetworkID::Mainnet); } + + #[test] + fn from_account() { + let lhs = SUT::from(Account::sample()); + assert_eq!( + lhs, + SUT::new( + "account_rdx12yy8n09a0w907vrjyj4hws2yptrm3rdjv84l9sr24e3w7pk7nuxst8", + DisplayName::new("Alice").unwrap(), + AppearanceID::new(0).unwrap(), + ) + ) + } } diff --git a/src/profile/supporting_types/account_for_display_uniffi_fn.rs b/crates/profile/src/supporting_types/account_for_display_uniffi_fn.rs similarity index 67% rename from src/profile/supporting_types/account_for_display_uniffi_fn.rs rename to crates/profile/src/supporting_types/account_for_display_uniffi_fn.rs index 6bcc2964c..af6dde0c9 100644 --- a/src/profile/supporting_types/account_for_display_uniffi_fn.rs +++ b/crates/profile/src/supporting_types/account_for_display_uniffi_fn.rs @@ -10,6 +10,13 @@ pub fn new_account_for_display_sample_other() -> AccountForDisplay { AccountForDisplay::sample_other() } +#[uniffi::export] +pub fn new_account_for_display_from_account( + account: Account, +) -> AccountForDisplay { + AccountForDisplay::from(account) +} + #[cfg(test)] mod tests { use super::*; @@ -31,4 +38,13 @@ mod tests { 2 ); } + + #[test] + fn test_new_account_for_display_from_account() { + let sut = Account::sample(); + assert_eq!( + new_account_for_display_from_account(sut.clone()), + AccountForDisplay::from(sut) + ); + } } diff --git a/src/profile/supporting_types/account_or_persona.rs b/crates/profile/src/supporting_types/account_or_persona.rs similarity index 92% rename from src/profile/supporting_types/account_or_persona.rs rename to crates/profile/src/supporting_types/account_or_persona.rs index e69537128..1b1166c85 100644 --- a/src/profile/supporting_types/account_or_persona.rs +++ b/crates/profile/src/supporting_types/account_or_persona.rs @@ -77,27 +77,27 @@ impl HasSampleValues for AccountOrPersona { } impl AccountOrPersona { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::from(Account::sample_mainnet()) } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::from(Persona::sample_mainnet_other()) } - pub(crate) fn sample_mainnet_third() -> Self { + pub fn sample_mainnet_third() -> Self { Self::from(Account::sample_mainnet_third()) } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::from(Account::sample_stokenet()) } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::from(Persona::sample_stokenet_other()) } - pub(crate) fn sample_stokenet_third() -> Self { + pub fn sample_stokenet_third() -> Self { Self::from(Account::sample_stokenet_third()) } } diff --git a/src/profile/supporting_types/account_or_persona_uniffi_fn.rs b/crates/profile/src/supporting_types/account_or_persona_uniffi_fn.rs similarity index 100% rename from src/profile/supporting_types/account_or_persona_uniffi_fn.rs rename to crates/profile/src/supporting_types/account_or_persona_uniffi_fn.rs diff --git a/src/profile/supporting_types/accounts_for_display.rs b/crates/profile/src/supporting_types/accounts_for_display.rs similarity index 100% rename from src/profile/supporting_types/accounts_for_display.rs rename to crates/profile/src/supporting_types/accounts_for_display.rs diff --git a/src/profile/supporting_types/accounts_or_personas.rs b/crates/profile/src/supporting_types/accounts_or_personas.rs similarity index 94% rename from src/profile/supporting_types/accounts_or_personas.rs rename to crates/profile/src/supporting_types/accounts_or_personas.rs index a91b399a7..44bbd790d 100644 --- a/src/profile/supporting_types/accounts_or_personas.rs +++ b/crates/profile/src/supporting_types/accounts_or_personas.rs @@ -19,7 +19,7 @@ impl HasSampleValues for AccountsOrPersonas { } impl AccountsOrPersonas { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::from_iter([ Account::sample_mainnet().into(), Persona::sample_mainnet().into(), @@ -30,7 +30,7 @@ impl AccountsOrPersonas { ]) } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::from_iter([ Persona::sample_stokenet().into(), Account::sample_stokenet().into(), diff --git a/src/profile/supporting_types/authorized_dapp_detailed.rs b/crates/profile/src/supporting_types/authorized_dapp_detailed.rs similarity index 100% rename from src/profile/supporting_types/authorized_dapp_detailed.rs rename to crates/profile/src/supporting_types/authorized_dapp_detailed.rs diff --git a/src/profile/supporting_types/authorized_dapp_detailed_uniffi_fn.rs b/crates/profile/src/supporting_types/authorized_dapp_detailed_uniffi_fn.rs similarity index 100% rename from src/profile/supporting_types/authorized_dapp_detailed_uniffi_fn.rs rename to crates/profile/src/supporting_types/authorized_dapp_detailed_uniffi_fn.rs diff --git a/src/profile/supporting_types/authorized_persona_detailed.rs b/crates/profile/src/supporting_types/authorized_persona_detailed.rs similarity index 100% rename from src/profile/supporting_types/authorized_persona_detailed.rs rename to crates/profile/src/supporting_types/authorized_persona_detailed.rs diff --git a/src/profile/supporting_types/authorized_persona_detailed_uniffi_fn.rs b/crates/profile/src/supporting_types/authorized_persona_detailed_uniffi_fn.rs similarity index 100% rename from src/profile/supporting_types/authorized_persona_detailed_uniffi_fn.rs rename to crates/profile/src/supporting_types/authorized_persona_detailed_uniffi_fn.rs diff --git a/crates/profile/src/supporting_types/decl_identified_vec_of_with_samples.rs b/crates/profile/src/supporting_types/decl_identified_vec_of_with_samples.rs new file mode 100644 index 000000000..4980e15da --- /dev/null +++ b/crates/profile/src/supporting_types/decl_identified_vec_of_with_samples.rs @@ -0,0 +1,163 @@ +use crate::prelude::*; + +#[macro_export] +macro_rules! decl_identified_vec_of { + ( + $( + #[doc = $expr: expr] + )* + $collection_type: ident, + $element_type: ident + ) => { + paste! { + $( + #[doc = $expr] + )* + #[derive(Clone, PartialEq, Eq, Default, Serialize, Deserialize, std::hash::Hash, derive_more::Display, derive_more::Debug)] + #[debug("{:?}", self.0)] + #[display("{}", self.0)] + #[serde(transparent)] + pub struct $collection_type(pub IdentifiedVecOf<$element_type>); + + impl From> for $collection_type { + fn from(value: IdentifiedVecOf<$element_type>) -> Self { + Self(value) + } + } + + uniffi::custom_newtype!($collection_type, IdentifiedVecOf<$element_type>); + + impl std::ops::Deref for $collection_type { + type Target = IdentifiedVecOf<$element_type>; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl std::ops::DerefMut for $collection_type { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + impl $collection_type { + #[inline] + pub fn iter(&self) -> IdentifiedVecOfIterator<$element_type> { + // IdentifiedVecOfIterator { + // ordered_map: self, + // index: self.0, + // } + self.0.iter() + } + + /// Creates a new empty `IdentifiedVecOf`. + pub fn new() -> Self { + IdentifiedVecOf::new().into() + } + + /// Creates a new `IdentifiedVecOf` with one single item. + pub fn just(item: $element_type) -> Self { + Self::from_iter([item]) + } + } + + impl FromIterator<$element_type> for $collection_type + { + fn from_iter>(iter: T) -> Self { + Self::from(IdentifiedVecOf::<$element_type>::from_iter(iter)) + } + } + + impl IntoIterator for $collection_type + { + type Item = $element_type; + type IntoIter = OwnedIdentifiedVecOfIterator<$element_type>; + + fn into_iter(self) -> Self::IntoIter { + // OwnedIdentifiedVecOfIterator { + // ordered_map: self.0, + // index: 0, + // } + self.0.into_iter() + } + } + + #[uniffi::export] + pub fn [< new_ $collection_type:snake _sample >]() -> $collection_type { + $collection_type::sample() + } + + #[uniffi::export] + pub fn [< new_ $collection_type:snake _sample_other >]() -> $collection_type { + $collection_type::sample_other() + } + + #[cfg(test)] + mod [< $collection_type:snake _tests >] { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = $collection_type; + + #[test] + fn test_ids() { + assert_eq!(SUT::sample().ids().into_iter().cloned().collect_vec(), SUT::sample().get_all().into_iter().map(|i| i.id()).collect_vec()); + } + } + + #[cfg(test)] + mod [< $collection_type:snake _uniffi_tests >] { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = $collection_type; + + #[test] + fn hash_of_samples() { + assert_eq!( + HashSet::::from_iter([ + [< new_ $collection_type:snake _sample >](), + [< new_ $collection_type:snake _sample_other >](), + // duplicates should get removed + [< new_ $collection_type:snake _sample >](), + [< new_ $collection_type:snake _sample_other >]() + ]) + .len(), + 2 + ); + } + + // #[test] + // fn manual_perform_uniffi_conversion_successful() { + // let test = |sut: SUT| { + // let ffi_side = >::lower(sut.clone()); + // let from_ffi = + // >::try_lift(ffi_side).unwrap(); + // assert_eq!(from_ffi, sut); + // }; + + // test(SUT::sample()); + // test(SUT::sample_other()); + // } + } + } + }; + ( + $( + #[doc = $expr: expr] + )* + $element_type: ident + ) => { + paste! { + decl_identified_vec_of!( + $( + #[doc = $expr] + )* + [< $element_type s>], + $element_type + ); + } + }; +} + +pub(crate) use decl_identified_vec_of; diff --git a/src/profile/supporting_types/detailed_authorized_personas.rs b/crates/profile/src/supporting_types/detailed_authorized_personas.rs similarity index 100% rename from src/profile/supporting_types/detailed_authorized_personas.rs rename to crates/profile/src/supporting_types/detailed_authorized_personas.rs diff --git a/src/core/types/identified_vec_of/identified_vec_of.rs b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of.rs similarity index 91% rename from src/core/types/identified_vec_of/identified_vec_of.rs rename to crates/profile/src/supporting_types/identified_vec_of/identified_vec_of.rs index 5dc5e8e9d..c0110220f 100644 --- a/src/core/types/identified_vec_of/identified_vec_of.rs +++ b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of.rs @@ -1,14 +1,9 @@ -use radix_rust::prelude::{IndexMap, IndexSet}; +use radix_rust::prelude::IndexMap; use crate::prelude::*; -use std::{ - any::TypeId, - fmt::{Debug, Display, Formatter}, - hash::Hasher, - ops::Index, -}; -use std::{hash::Hash, ops::DerefMut}; +use std::hash::Hash; +use std::{fmt::Debug, hash::Hasher, ops::Index}; /// A collection which **retains the insertion order** of its **unique** [`Identifiable`] /// items, with **constant time** look up of an item by its `id` - a stable key diff --git a/src/core/types/identified_vec_of/identified_vec_of_display_debug.rs b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_display_debug.rs similarity index 100% rename from src/core/types/identified_vec_of/identified_vec_of_display_debug.rs rename to crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_display_debug.rs diff --git a/src/core/types/identified_vec_of/identified_vec_of_iterator.rs b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_iterator.rs similarity index 68% rename from src/core/types/identified_vec_of/identified_vec_of_iterator.rs rename to crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_iterator.rs index f7fed75b8..8aa026b19 100644 --- a/src/core/types/identified_vec_of/identified_vec_of_iterator.rs +++ b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_iterator.rs @@ -38,6 +38,20 @@ impl<'a, V: Debug + PartialEq + Eq + Clone + Identifiable> IntoIterator } } +impl IntoIterator + for IdentifiedVecOf +{ + type Item = V; + type IntoIter = OwnedIdentifiedVecOfIterator; + + fn into_iter(self) -> Self::IntoIter { + OwnedIdentifiedVecOfIterator { + ordered_map: self, + index: 0, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct IdentifiedVecOfIterator< 'a, @@ -63,6 +77,30 @@ impl<'a, V: Debug + PartialEq + Eq + Clone + Identifiable> Iterator } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OwnedIdentifiedVecOfIterator< + V: Debug + PartialEq + Eq + Clone + Identifiable, +> { + ordered_map: IdentifiedVecOf, + index: usize, +} + +impl Iterator + for OwnedIdentifiedVecOfIterator +{ + type Item = V; + + fn next(&mut self) -> Option { + if self.index < self.ordered_map.len() { + let elem = self.ordered_map.0.get_index(self.index); + self.index += 1; + elem.map(|pair| pair.1.clone()) + } else { + None + } + } +} + #[cfg(test)] mod tests { @@ -75,7 +113,7 @@ mod tests { #[test] fn into_from_iter() { let sut = SUT::sample(); - let iter = sut.into_iter(); + let iter = sut.clone().into_iter(); let from_iter = SUT::from_iter(iter); assert_eq!(from_iter, sut) } diff --git a/src/core/types/identified_vec_of/identified_vec_of_modify.rs b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_modify.rs similarity index 95% rename from src/core/types/identified_vec_of/identified_vec_of_modify.rs rename to crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_modify.rs index 637f52024..70fc2a9a2 100644 --- a/src/core/types/identified_vec_of/identified_vec_of_modify.rs +++ b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_modify.rs @@ -60,6 +60,10 @@ impl IdentifiedVecOf { (true, self.len() - 1) } + pub fn extend(&mut self, iter: impl IntoIterator) { + self.0.extend(iter.into_iter().map(|i| (i.id(), i))) + } + /// Remove and return the item with the `id`. /// /// Like `Vec::remove``, the item is removed by shifting all of the elements @@ -247,9 +251,9 @@ mod tests { fn update_with_for_existing() { let foobar = User::new(0, "Foobar"); let mut sut = SUT::sample(); - assert_eq!(sut.get_id(&0), Some(&User::alice())); + assert_eq!(sut.get_id(0), Some(&User::alice())); assert!(sut.update_with(&0, |u| { *u = foobar.clone() })); - assert_eq!(sut.get_id(&0), Some(&foobar)); + assert_eq!(sut.get_id(0), Some(&foobar)); } #[test] @@ -262,14 +266,14 @@ mod tests { fn test_try_try_update_with_succeeds() { let foobar = User::new(0, "Foobar"); let mut sut = SUT::sample(); - assert_eq!(sut.get_id(&0), Some(&User::alice())); + assert_eq!(sut.get_id(0), Some(&User::alice())); assert!(sut .try_try_update_with(&0, |u| { *u = foobar.clone(); Ok(()) }) .is_ok()); - assert_eq!(sut.get_id(&0), Some(&foobar)); + assert_eq!(sut.get_id(0), Some(&foobar)); } #[test] @@ -288,9 +292,9 @@ mod tests { fn test_try_update_with_success() { let foobar = User::new(0, "Foobar"); let mut sut = SUT::sample(); - assert_eq!(sut.get_id(&0), Some(&User::alice())); + assert_eq!(sut.get_id(0), Some(&User::alice())); assert!(sut.try_update_with(&0, |u| { *u = foobar.clone() }).is_ok()); - assert_eq!(sut.get_id(&0), Some(&foobar)); + assert_eq!(sut.get_id(0), Some(&foobar)); } #[test] diff --git a/src/core/types/identified_vec_of/identified_vec_of_query.rs b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_query.rs similarity index 85% rename from src/core/types/identified_vec_of/identified_vec_of_query.rs rename to crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_query.rs index c397b1221..249f47e5d 100644 --- a/src/core/types/identified_vec_of/identified_vec_of_query.rs +++ b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_query.rs @@ -1,4 +1,5 @@ use radix_rust::prelude::IndexSet; +use std::borrow::Borrow; use crate::prelude::*; @@ -25,8 +26,8 @@ impl IdentifiedVecOf { } /// Return `true`` if an item with `id` exists in the collection. - pub fn contains_id(&self, id: &V::ID) -> bool { - self.0.contains_key(id) + pub fn contains_id(&self, id: impl Borrow) -> bool { + self.0.contains_key(id.borrow()) } /// Get an item by index @@ -41,8 +42,8 @@ impl IdentifiedVecOf { /// Return a reference to the item, if it is present, else `None``. /// /// Computes in **O(1)** time (average). - pub fn get_id(&self, id: &V::ID) -> Option<&V> { - self.0.get(id) + pub fn get_id(&self, id: impl Borrow) -> Option<&V> { + self.0.get(id.borrow()) } /// Return a Vec of references to the items of the collection, in their order. @@ -73,9 +74,12 @@ mod tests { #[test] fn get_id() { let sut = SUT::sample(); - assert_eq!(sut.get_id(&0), Some(&User::alice())); - assert_eq!(sut.get_id(&2), Some(&User::carol())); - assert_eq!(sut.get_id(&200), None); + assert_eq!(sut.get_id(0), Some(&User::alice())); + assert_eq!( + sut.get_id(2), /* Can also omit & */ + Some(&User::carol()) + ); + assert_eq!(sut.get_id(200), None); } #[test] @@ -103,8 +107,8 @@ mod tests { #[test] fn contains_id() { let sut = SUT::sample(); - assert!(sut.contains_id(&0)); - assert!(!sut.contains_id(&200)); + assert!(sut.contains_id(0)); + assert!(!sut.contains_id(200)); } #[test] diff --git a/src/core/types/identified_vec_of/identified_vec_of_serde.rs b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_serde.rs similarity index 98% rename from src/core/types/identified_vec_of/identified_vec_of_serde.rs rename to crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_serde.rs index 47412cdf8..0e60b3a02 100644 --- a/src/core/types/identified_vec_of/identified_vec_of_serde.rs +++ b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_serde.rs @@ -1,5 +1,3 @@ -use std::any::TypeId; - use crate::prelude::*; use super::{export_identified_vec_of, import_identified_vec_of_from}; diff --git a/src/core/types/identified_vec_of/identified_vec_of_uniffi_converter.rs b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_uniffi_converter.rs similarity index 98% rename from src/core/types/identified_vec_of/identified_vec_of_uniffi_converter.rs rename to crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_uniffi_converter.rs index a51d33c8c..9e76a5e61 100644 --- a/src/core/types/identified_vec_of/identified_vec_of_uniffi_converter.rs +++ b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_uniffi_converter.rs @@ -1,6 +1,5 @@ use crate::prelude::*; -use radix_rust::prelude::IndexMap; -use std::any::TypeId as StdTypeId; + use uniffi::TypeId as UFTypeId; use uniffi::{ check_remaining, diff --git a/src/core/types/identified_vec_of/identified_vec_of_validation_import_export.rs b/crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_validation_import_export.rs similarity index 100% rename from src/core/types/identified_vec_of/identified_vec_of_validation_import_export.rs rename to crates/profile/src/supporting_types/identified_vec_of/identified_vec_of_validation_import_export.rs diff --git a/src/core/types/identified_vec_of/mod.rs b/crates/profile/src/supporting_types/identified_vec_of/mod.rs similarity index 64% rename from src/core/types/identified_vec_of/mod.rs rename to crates/profile/src/supporting_types/identified_vec_of/mod.rs index fa2f877f5..871ef97a7 100644 --- a/src/core/types/identified_vec_of/mod.rs +++ b/crates/profile/src/supporting_types/identified_vec_of/mod.rs @@ -1,4 +1,3 @@ -mod identifiable; mod identified_vec_of; mod identified_vec_of_display_debug; mod identified_vec_of_iterator; @@ -14,12 +13,8 @@ mod user; #[cfg(test)] use user::*; -pub use identifiable::*; pub use identified_vec_of::*; -pub use identified_vec_of_display_debug::*; + pub use identified_vec_of_iterator::*; -pub use identified_vec_of_modify::*; -pub use identified_vec_of_query::*; -pub use identified_vec_of_serde::*; -pub use identified_vec_of_uniffi_converter::*; + use identified_vec_of_validation_import_export::*; diff --git a/src/core/types/identified_vec_of/user.rs b/crates/profile/src/supporting_types/identified_vec_of/user.rs similarity index 100% rename from src/core/types/identified_vec_of/user.rs rename to crates/profile/src/supporting_types/identified_vec_of/user.rs diff --git a/src/profile/supporting_types/mod.rs b/crates/profile/src/supporting_types/mod.rs similarity index 82% rename from src/profile/supporting_types/mod.rs rename to crates/profile/src/supporting_types/mod.rs index 523308db8..0cba36fc0 100644 --- a/src/profile/supporting_types/mod.rs +++ b/crates/profile/src/supporting_types/mod.rs @@ -10,6 +10,8 @@ mod authorized_persona_detailed; mod authorized_persona_detailed_uniffi_fn; mod decl_identified_vec_of_with_samples; mod detailed_authorized_personas; +mod identified_vec_of; +mod on_same_network_validating; pub use account_for_display::*; pub use account_for_display_uniffi_fn::*; @@ -21,5 +23,7 @@ pub use authorized_dapp_detailed::*; pub use authorized_dapp_detailed_uniffi_fn::*; pub use authorized_persona_detailed::*; pub use authorized_persona_detailed_uniffi_fn::*; -pub use decl_identified_vec_of_with_samples::*; +pub(crate) use decl_identified_vec_of_with_samples::*; pub use detailed_authorized_personas::*; +pub use identified_vec_of::*; +pub use on_same_network_validating::*; diff --git a/crates/profile/src/supporting_types/on_same_network_validating.rs b/crates/profile/src/supporting_types/on_same_network_validating.rs new file mode 100644 index 000000000..4982b74a4 --- /dev/null +++ b/crates/profile/src/supporting_types/on_same_network_validating.rs @@ -0,0 +1,35 @@ +use crate::prelude::*; + +pub trait OnSameNetworkValidating: + Clone + IntoIterator +{ + type Element: IsNetworkAware; + + fn is_empty(&self) -> bool; + + fn assert_elements_not_empty_and_on_same_network( + &self, + ) -> Result { + self.assert_elements_on_same_network() + .and_then(|x| x.ok_or(CommonError::ExpectedNonEmptyCollection)) + } + + fn assert_elements_on_same_network(&self) -> Result> { + if self.is_empty() { + return Ok(None); + } + let network_id = self.clone().into_iter().next().unwrap().network_id(); + self.clone().into_iter().try_for_each(|e| { + if e.network_id() == network_id { + Ok(()) + } else { + Err(CommonError::NetworkDiscrepancy { + expected: network_id.discriminant(), + actual: e.network_id().discriminant(), + }) + } + })?; + + Ok(Some(network_id)) + } +} diff --git a/src/profile/v100/app_preferences/app_display_settings/app_display_settings.rs b/crates/profile/src/v100/app_preferences/app_display_settings/app_display_settings.rs similarity index 100% rename from src/profile/v100/app_preferences/app_display_settings/app_display_settings.rs rename to crates/profile/src/v100/app_preferences/app_display_settings/app_display_settings.rs diff --git a/src/profile/v100/app_preferences/app_display_settings/fiat_currency.rs b/crates/profile/src/v100/app_preferences/app_display_settings/fiat_currency.rs similarity index 100% rename from src/profile/v100/app_preferences/app_display_settings/fiat_currency.rs rename to crates/profile/src/v100/app_preferences/app_display_settings/fiat_currency.rs diff --git a/src/profile/v100/app_preferences/app_display_settings/fiat_currency_uniffi_fn.rs b/crates/profile/src/v100/app_preferences/app_display_settings/fiat_currency_uniffi_fn.rs similarity index 100% rename from src/profile/v100/app_preferences/app_display_settings/fiat_currency_uniffi_fn.rs rename to crates/profile/src/v100/app_preferences/app_display_settings/fiat_currency_uniffi_fn.rs diff --git a/src/profile/v100/app_preferences/app_display_settings/mod.rs b/crates/profile/src/v100/app_preferences/app_display_settings/mod.rs similarity index 100% rename from src/profile/v100/app_preferences/app_display_settings/mod.rs rename to crates/profile/src/v100/app_preferences/app_display_settings/mod.rs diff --git a/src/profile/v100/app_preferences/app_preferences.rs b/crates/profile/src/v100/app_preferences/app_preferences.rs similarity index 91% rename from src/profile/v100/app_preferences/app_preferences.rs rename to crates/profile/src/v100/app_preferences/app_preferences.rs index 6feb2b7b2..626a375e3 100644 --- a/src/profile/v100/app_preferences/app_preferences.rs +++ b/crates/profile/src/v100/app_preferences/app_preferences.rs @@ -135,16 +135,8 @@ mod tests { "isCurrencyAmountVisible": true }, "gateways": { - "current": "https://rcnet-v3.radixdlt.com/", + "current": "https://mainnet.radixdlt.com/", "saved": [ - { - "network": { - "name": "zabanet", - "id": 14, - "displayDescription": "RCnet-V3 (Test Network)" - }, - "url": "https://rcnet-v3.radixdlt.com/" - }, { "network": { "name": "mainnet", diff --git a/src/profile/v100/app_preferences/app_preferences_uniffi_fn.rs b/crates/profile/src/v100/app_preferences/app_preferences_uniffi_fn.rs similarity index 100% rename from src/profile/v100/app_preferences/app_preferences_uniffi_fn.rs rename to crates/profile/src/v100/app_preferences/app_preferences_uniffi_fn.rs diff --git a/src/profile/v100/app_preferences/gateways/gateway.rs b/crates/profile/src/v100/app_preferences/gateways/gateway.rs similarity index 98% rename from src/profile/v100/app_preferences/gateways/gateway.rs rename to crates/profile/src/v100/app_preferences/gateways/gateway.rs index a99610ff9..950a92cb6 100644 --- a/src/profile/v100/app_preferences/gateways/gateway.rs +++ b/crates/profile/src/v100/app_preferences/gateways/gateway.rs @@ -1,5 +1,4 @@ use crate::prelude::*; -use std::ops::Deref; /// A gateway to some Radix Network, which is a high level REST API which clients (wallets) can /// consume in order to query asset balances and submit transactions. @@ -68,7 +67,7 @@ impl Gateway { } impl Gateway { - pub(crate) fn declare(url: &str, id: NetworkID) -> Self { + pub fn declare(url: &str, id: NetworkID) -> Self { Self::new(url.to_string(), id).expect("Valid").clone() } } diff --git a/src/profile/v100/app_preferences/gateways/gateway_uniffi_fn.rs b/crates/profile/src/v100/app_preferences/gateways/gateway_uniffi_fn.rs similarity index 100% rename from src/profile/v100/app_preferences/gateways/gateway_uniffi_fn.rs rename to crates/profile/src/v100/app_preferences/gateways/gateway_uniffi_fn.rs diff --git a/src/profile/v100/app_preferences/gateways/mod.rs b/crates/profile/src/v100/app_preferences/gateways/mod.rs similarity index 100% rename from src/profile/v100/app_preferences/gateways/mod.rs rename to crates/profile/src/v100/app_preferences/gateways/mod.rs diff --git a/src/profile/v100/app_preferences/gateways/network_definition.rs b/crates/profile/src/v100/app_preferences/gateways/network_definition.rs similarity index 100% rename from src/profile/v100/app_preferences/gateways/network_definition.rs rename to crates/profile/src/v100/app_preferences/gateways/network_definition.rs diff --git a/src/profile/v100/app_preferences/gateways/network_definition_uniffi_fn.rs b/crates/profile/src/v100/app_preferences/gateways/network_definition_uniffi_fn.rs similarity index 100% rename from src/profile/v100/app_preferences/gateways/network_definition_uniffi_fn.rs rename to crates/profile/src/v100/app_preferences/gateways/network_definition_uniffi_fn.rs diff --git a/src/profile/v100/app_preferences/gateways/saved_gateways.rs b/crates/profile/src/v100/app_preferences/gateways/saved_gateways.rs similarity index 73% rename from src/profile/v100/app_preferences/gateways/saved_gateways.rs rename to crates/profile/src/v100/app_preferences/gateways/saved_gateways.rs index eead0c3da..5557f925e 100644 --- a/src/profile/v100/app_preferences/gateways/saved_gateways.rs +++ b/crates/profile/src/v100/app_preferences/gateways/saved_gateways.rs @@ -110,53 +110,21 @@ impl SavedGateways { } } -impl SavedGateways { - /// Changes the current Gateway to `to`, if it is not already the current. If `to` is - /// not a new Gateway, it will be removed from. Returns `Ok(false)` if `to` was already - /// the `current`, returns `Ok(true)` if `to` was not already `current`. - pub fn change_current(&mut self, to: Gateway) -> Result { - if self.current == to { - return Ok(false); - } - let old_current = &self.current; - let was_inserted = self.append(old_current.clone()); - if !was_inserted { - return Err( - CommonError::GatewaysDiscrepancyOtherShouldNotContainCurrent, - ); - } - self.other.remove_id(&to.id()); - self.current = to; - Ok(true) - } - - /// Appends `gateway` to the `other` list, without changing the `current` Gateway. - /// If `other` already contains `gateway` then `(false, other.len())` is returned. - /// If `other` was new then `(true, index_of_new)` is returned. - /// - /// - Returns: `true` if it was added, `false` if it was already present (noop) - pub fn append(&mut self, gateway: Gateway) -> bool { - self.other.append(gateway).0 - } -} - impl Default for SavedGateways { fn default() -> Self { - Self::new_with_other(Gateway::mainnet(), vec![Gateway::stokenet()]) + Self::new_with_other(Gateway::mainnet(), [Gateway::stokenet()]) .expect("Stokenet and Mainnet should have different NetworkIDs.") } } impl HasSampleValues for SavedGateways { fn sample() -> Self { - let mut gateways = Self::new(Gateway::rcnet()); - gateways.append(Gateway::mainnet()); - gateways.append(Gateway::stokenet()); - gateways + SavedGateways::default() } fn sample_other() -> Self { - SavedGateways::default() + Self::new_with_other(Gateway::stokenet(), [Gateway::mainnet()]) + .expect("Stokenet and Mainnet should have different NetworkIDs.") } } @@ -172,7 +140,7 @@ impl HasSampleValues for Gateways { #[cfg(test)] mod tests { - use crate::prelude::*; + use super::*; #[allow(clippy::upper_case_acronyms)] type SUT = SavedGateways; @@ -188,39 +156,27 @@ mod tests { assert_ne!(SUT::sample(), SUT::sample_other()); } - #[test] - fn change_current_to_existing() { - let mut sut = SUT::default(); - assert_eq!(sut.current.network.id, NetworkID::Mainnet); - assert_eq!(sut.change_current(Gateway::stokenet()), Ok(true)); - assert_eq!(sut.current.network.id, NetworkID::Stokenet); - } - #[test] fn append() { let mut sut = SUT::sample(); assert!(!sut.append(Gateway::mainnet())); + assert!(!sut.append(Gateway::stokenet())); assert_eq!(sut, SUT::sample()); assert!(sut.append(Gateway::kisharnet())); - assert_ne!(sut, SUT::sample()); - } - - #[test] - fn new_throw_gateways_discrepancy_other_should_not_contain_current() { assert_eq!( - SUT::new_with_other(Gateway::mainnet(), vec![Gateway::mainnet()]), - Err(CommonError::GatewaysDiscrepancyOtherShouldNotContainCurrent) + sut, + SUT::new_with_other( + Gateway::mainnet(), + [Gateway::stokenet(), Gateway::kisharnet()] + ) + .unwrap() ); } #[test] - fn change_throw_gateways_discrepancy_other_should_not_contain_current() { - let mut impossible = SUT { - current: Gateway::mainnet(), - other: Gateways::from_iter([Gateway::mainnet()]), - }; + fn new_throw_gateways_discrepancy_other_should_not_contain_current() { assert_eq!( - impossible.change_current(Gateway::stokenet()), + SUT::new_with_other(Gateway::mainnet(), vec![Gateway::mainnet()]), Err(CommonError::GatewaysDiscrepancyOtherShouldNotContainCurrent) ); } @@ -229,7 +185,10 @@ mod tests { fn change_current_to_current() { let mut sut = SUT::default(); assert_eq!(sut.current.network.id, NetworkID::Mainnet); - assert_eq!(sut.change_current(Gateway::mainnet()), Ok(false)); + assert_eq!( + sut.change_current(Gateway::mainnet()), + ChangeGatewayOutcome::NoChange + ); assert_eq!(sut.current.network.id, NetworkID::Mainnet); } @@ -244,18 +203,6 @@ mod tests { assert!(!sut.is_empty()); } - #[test] - fn change_current_to_new() { - let mut sut = SUT::default(); - assert_eq!(sut.current.network.id, NetworkID::Mainnet); - assert_eq!(sut.change_current(Gateway::nebunet()), Ok(true)); - assert_eq!(sut.current.network.id, NetworkID::Nebunet); - assert_eq!( - sut.other.items(), - [Gateway::stokenet(), Gateway::mainnet()] - ); - } - #[test] fn json_roundtrip() { let sut = SUT::sample(); @@ -264,17 +211,8 @@ mod tests { &sut, r#" { - "current": "https://rcnet-v3.radixdlt.com/", + "current": "https://mainnet.radixdlt.com/", "saved": [ - { - "network": - { - "name": "zabanet", - "id": 14, - "displayDescription": "RCnet-V3 (Test Network)" - }, - "url": "https://rcnet-v3.radixdlt.com/" - }, { "network": { diff --git a/src/profile/v100/app_preferences/gateways/saved_gateways_uniffi_fn.rs b/crates/profile/src/v100/app_preferences/gateways/saved_gateways_uniffi_fn.rs similarity index 98% rename from src/profile/v100/app_preferences/gateways/saved_gateways_uniffi_fn.rs rename to crates/profile/src/v100/app_preferences/gateways/saved_gateways_uniffi_fn.rs index 0263d0744..d57116e20 100644 --- a/src/profile/v100/app_preferences/gateways/saved_gateways_uniffi_fn.rs +++ b/crates/profile/src/v100/app_preferences/gateways/saved_gateways_uniffi_fn.rs @@ -38,7 +38,7 @@ pub fn new_saved_gateways_changing_current( gateways: &SavedGateways, ) -> Result { let mut gateways = gateways.clone(); - gateways.change_current(to)?; + let _ = gateways.change_current(to); Ok(gateways) } diff --git a/src/profile/v100/app_preferences/mod.rs b/crates/profile/src/v100/app_preferences/mod.rs similarity index 100% rename from src/profile/v100/app_preferences/mod.rs rename to crates/profile/src/v100/app_preferences/mod.rs diff --git a/src/profile/v100/app_preferences/security.rs b/crates/profile/src/v100/app_preferences/security.rs similarity index 94% rename from src/profile/v100/app_preferences/security.rs rename to crates/profile/src/v100/app_preferences/security.rs index ca3293430..46c44a852 100644 --- a/src/profile/v100/app_preferences/security.rs +++ b/crates/profile/src/v100/app_preferences/security.rs @@ -3,7 +3,18 @@ use crate::prelude::*; // FIXME: MFA this is in fact not used, so ok to be a `bool` for now. The AppPreferences Security type has // a field `structure_configuration_references` but no client can populate it yet, so the list will always // be empty, thus save to used a serializable trivial type such as `bool` as a sample for now. -pub type SecurityStructureConfigurationReference = bool; +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + PartialEq, + Eq, + Hash, + uniffi::Record, +)] +pub struct SecurityStructureConfigurationReference; impl Identifiable for SecurityStructureConfigurationReference { type ID = Self; diff --git a/src/profile/v100/app_preferences/transaction_preferences.rs b/crates/profile/src/v100/app_preferences/transaction_preferences.rs similarity index 100% rename from src/profile/v100/app_preferences/transaction_preferences.rs rename to crates/profile/src/v100/app_preferences/transaction_preferences.rs diff --git a/src/profile/v100/entity/account/account.rs b/crates/profile/src/v100/entity/account/account.rs similarity index 95% rename from src/profile/v100/entity/account/account.rs rename to crates/profile/src/v100/entity/account/account.rs index f45889d3d..389fdc968 100644 --- a/src/profile/v100/entity/account/account.rs +++ b/crates/profile/src/v100/entity/account/account.rs @@ -85,32 +85,6 @@ impl IsNetworkAware for Account { } } -impl Account { - pub fn new( - account_creating_factor_instance: HDFactorInstanceAccountCreation, - display_name: DisplayName, - appearance_id: AppearanceID, - ) -> Self { - let address = - AccountAddress::from_hd_factor_instance_virtual_entity_creation( - account_creating_factor_instance.clone(), - ); - Self { - network_id: account_creating_factor_instance.network_id(), - address, - display_name, - security_state: - UnsecuredEntityControl::with_entity_creating_factor_instance( - account_creating_factor_instance, - ) - .into(), - appearance_id, - flags: EntityFlags::default(), - on_ledger_settings: OnLedgerSettings::default(), - } - } -} - impl Identifiable for Account { type ID = AccountAddress; @@ -178,7 +152,7 @@ impl Account { ) -> Self { let mwp = MnemonicWithPassphrase::sample(); let bdfs = - DeviceFactorSource::babylon(true, &mwp, WalletClientModel::Iphone); + DeviceFactorSource::babylon(true, &mwp, &DeviceInfo::sample()); let private_hd_factor_source = PrivateHierarchicalDeterministicFactorSource::new(mwp, bdfs); let account_creating_factor_instance = private_hd_factor_source diff --git a/src/profile/v100/entity/account/account_uniffi_fn.rs b/crates/profile/src/v100/entity/account/account_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/account/account_uniffi_fn.rs rename to crates/profile/src/v100/entity/account/account_uniffi_fn.rs diff --git a/src/profile/v100/entity/account/mod.rs b/crates/profile/src/v100/entity/account/mod.rs similarity index 100% rename from src/profile/v100/entity/account/mod.rs rename to crates/profile/src/v100/entity/account/mod.rs diff --git a/src/profile/v100/entity/account/on_ledger_settings/mod.rs b/crates/profile/src/v100/entity/account/on_ledger_settings/mod.rs similarity index 100% rename from src/profile/v100/entity/account/on_ledger_settings/mod.rs rename to crates/profile/src/v100/entity/account/on_ledger_settings/mod.rs diff --git a/src/profile/v100/entity/account/on_ledger_settings/on_ledger_settings.rs b/crates/profile/src/v100/entity/account/on_ledger_settings/on_ledger_settings.rs similarity index 100% rename from src/profile/v100/entity/account/on_ledger_settings/on_ledger_settings.rs rename to crates/profile/src/v100/entity/account/on_ledger_settings/on_ledger_settings.rs diff --git a/src/profile/v100/entity/account/on_ledger_settings/on_ledger_settings_uniffi_fn.rs b/crates/profile/src/v100/entity/account/on_ledger_settings/on_ledger_settings_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/account/on_ledger_settings/on_ledger_settings_uniffi_fn.rs rename to crates/profile/src/v100/entity/account/on_ledger_settings/on_ledger_settings_uniffi_fn.rs diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception.rs b/crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception.rs similarity index 86% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception.rs rename to crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception.rs index 3a149dae2..127160631 100644 --- a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception.rs +++ b/crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception.rs @@ -1,5 +1,7 @@ use crate::prelude::*; +use radix_engine_interface::blueprints::account::AccountSetResourcePreferenceInput as ScryptoAccountSetResourcePreferenceInput; + /// The specific Asset exception rule, which overrides the general /// `deposit_rule` of a `ThirdPartyDeposits` settings. #[derive( @@ -24,6 +26,21 @@ pub struct AssetException { pub exception_rule: DepositAddressExceptionRule, } +impl From for ScryptoAccountSetResourcePreferenceInput { + fn from(value: AssetException) -> Self { + Self { + resource_address: value.address.into(), + resource_preference: value.exception_rule.into(), + } + } +} + +impl From for ScryptoManifestValue { + fn from(value: AssetException) -> Self { + ScryptoManifestValue::from(value.address) + } +} + impl HasSampleValues for AssetException { fn sample() -> Self { Self::new( diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception_uniffi_fn.rs b/crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception_uniffi_fn.rs rename to crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception_uniffi_fn.rs diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/assets_exception_list.rs b/crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/assets_exception_list.rs similarity index 100% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/assets_exception_list.rs rename to crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/assets_exception_list.rs diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/deposit_address_exception_rule.rs b/crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/deposit_address_exception_rule.rs similarity index 75% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/deposit_address_exception_rule.rs rename to crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/deposit_address_exception_rule.rs index 64fccc1bf..9cce14055 100644 --- a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/deposit_address_exception_rule.rs +++ b/crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/deposit_address_exception_rule.rs @@ -25,6 +25,19 @@ pub enum DepositAddressExceptionRule { Deny, } +impl From for ScryptoResourcePreference { + fn from(value: DepositAddressExceptionRule) -> Self { + match value { + DepositAddressExceptionRule::Allow => { + ScryptoResourcePreference::Allowed + } + DepositAddressExceptionRule::Deny => { + ScryptoResourcePreference::Disallowed + } + } + } +} + #[cfg(test)] mod tests { use crate::prelude::*; diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/depositors_allow_list.rs b/crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/depositors_allow_list.rs similarity index 100% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/depositors_allow_list.rs rename to crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/depositors_allow_list.rs diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/mod.rs b/crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/mod.rs similarity index 63% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/mod.rs rename to crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/mod.rs index 60c9dc598..9a56e6bb6 100644 --- a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/mod.rs +++ b/crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/mod.rs @@ -2,11 +2,8 @@ mod asset_exception; mod asset_exception_uniffi_fn; mod assets_exception_list; mod deposit_address_exception_rule; -mod deposit_rule; -mod deposit_rule_uniffi_fn; mod depositors_allow_list; -mod resource_or_non_fungible; -mod resource_or_non_fungible_uniffi_fn; + mod third_party_deposits; mod third_party_deposits_uniffi_fn; @@ -14,10 +11,7 @@ pub use asset_exception::*; pub use asset_exception_uniffi_fn::*; pub use assets_exception_list::*; pub use deposit_address_exception_rule::*; -pub use deposit_rule::*; -pub use deposit_rule_uniffi_fn::*; pub use depositors_allow_list::*; -pub use resource_or_non_fungible::*; -pub use resource_or_non_fungible_uniffi_fn::*; + pub use third_party_deposits::*; pub use third_party_deposits_uniffi_fn::*; diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits.rs b/crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits.rs similarity index 100% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits.rs rename to crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits.rs diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits_uniffi_fn.rs b/crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits_uniffi_fn.rs rename to crates/profile/src/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits_uniffi_fn.rs diff --git a/src/profile/v100/entity/display_name.rs b/crates/profile/src/v100/entity/display_name.rs similarity index 100% rename from src/profile/v100/entity/display_name.rs rename to crates/profile/src/v100/entity/display_name.rs diff --git a/src/profile/v100/entity/display_name_uniffi_fn.rs b/crates/profile/src/v100/entity/display_name_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/display_name_uniffi_fn.rs rename to crates/profile/src/v100/entity/display_name_uniffi_fn.rs diff --git a/src/profile/v100/entity/entity_flag.rs b/crates/profile/src/v100/entity/entity_flag.rs similarity index 100% rename from src/profile/v100/entity/entity_flag.rs rename to crates/profile/src/v100/entity/entity_flag.rs diff --git a/src/profile/v100/entity/entity_flag_uniffi_fn.rs b/crates/profile/src/v100/entity/entity_flag_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/entity_flag_uniffi_fn.rs rename to crates/profile/src/v100/entity/entity_flag_uniffi_fn.rs diff --git a/src/profile/v100/entity/entity_flags.rs b/crates/profile/src/v100/entity/entity_flags.rs similarity index 100% rename from src/profile/v100/entity/entity_flags.rs rename to crates/profile/src/v100/entity/entity_flags.rs diff --git a/src/profile/v100/entity/is_entity.rs b/crates/profile/src/v100/entity/is_entity.rs similarity index 100% rename from src/profile/v100/entity/is_entity.rs rename to crates/profile/src/v100/entity/is_entity.rs diff --git a/src/profile/v100/entity/mod.rs b/crates/profile/src/v100/entity/mod.rs similarity index 85% rename from src/profile/v100/entity/mod.rs rename to crates/profile/src/v100/entity/mod.rs index 0d1d64eb6..7ba245d51 100644 --- a/src/profile/v100/entity/mod.rs +++ b/crates/profile/src/v100/entity/mod.rs @@ -1,4 +1,3 @@ -mod abstract_entity_type; mod account; mod display_name; mod display_name_uniffi_fn; @@ -8,7 +7,6 @@ mod entity_flags; mod is_entity; mod persona; -pub use abstract_entity_type::*; pub use account::*; pub use display_name::*; pub use display_name_uniffi_fn::*; diff --git a/src/profile/v100/entity/persona/mod.rs b/crates/profile/src/v100/entity/persona/mod.rs similarity index 100% rename from src/profile/v100/entity/persona/mod.rs rename to crates/profile/src/v100/entity/persona/mod.rs diff --git a/src/profile/v100/entity/persona/persona.rs b/crates/profile/src/v100/entity/persona/persona.rs similarity index 95% rename from src/profile/v100/entity/persona/persona.rs rename to crates/profile/src/v100/entity/persona/persona.rs index 3b1d7d3e7..84fda5f73 100644 --- a/src/profile/v100/entity/persona/persona.rs +++ b/crates/profile/src/v100/entity/persona/persona.rs @@ -75,32 +75,6 @@ impl IsNetworkAware for Persona { } } -impl Persona { - /// Creates a new `Persona`, if `persona_data` is `None`, an empty object will be created. - pub fn new( - persona_creating_factor_instance: HDFactorInstanceIdentityCreation, - display_name: DisplayName, - persona_data: impl Into>, - ) -> Self { - let address = - IdentityAddress::from_hd_factor_instance_virtual_entity_creation( - persona_creating_factor_instance.clone(), - ); - Self { - network_id: persona_creating_factor_instance.network_id(), - address, - display_name, - security_state: - UnsecuredEntityControl::with_entity_creating_factor_instance( - persona_creating_factor_instance, - ) - .into(), - flags: EntityFlags::default(), - persona_data: persona_data.into().unwrap_or_default(), - } - } -} - impl Persona { #[cfg(not(tarpaulin_include))] // false negative fn sample_at_index_name_network( @@ -118,7 +92,7 @@ impl Persona { { let mwp = MnemonicWithPassphrase::sample(); let bdfs = - DeviceFactorSource::babylon(true, &mwp, WalletClientModel::Iphone); + DeviceFactorSource::babylon(true, &mwp, &DeviceInfo::sample()); let private_hd_factor_source = PrivateHierarchicalDeterministicFactorSource::new(mwp, bdfs); diff --git a/src/profile/v100/entity/persona/persona_data/collection_of_email_addresses.rs b/crates/profile/src/v100/entity/persona/persona_data/collection_of_email_addresses.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/collection_of_email_addresses.rs rename to crates/profile/src/v100/entity/persona/persona_data/collection_of_email_addresses.rs diff --git a/src/profile/v100/entity/persona/persona_data/collection_of_phone_numbers.rs b/crates/profile/src/v100/entity/persona/persona_data/collection_of_phone_numbers.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/collection_of_phone_numbers.rs rename to crates/profile/src/v100/entity/persona/persona_data/collection_of_phone_numbers.rs diff --git a/src/profile/v100/entity/persona/persona_data/entry_kinds/mod.rs b/crates/profile/src/v100/entity/persona/persona_data/entry_kinds/mod.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/entry_kinds/mod.rs rename to crates/profile/src/v100/entity/persona/persona_data/entry_kinds/mod.rs diff --git a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address.rs b/crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address.rs rename to crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address.rs diff --git a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address_uniffi_fn.rs b/crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address_uniffi_fn.rs rename to crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_email_address_uniffi_fn.rs diff --git a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name.rs b/crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name.rs rename to crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name.rs diff --git a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name_uniffi_fn.rs b/crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name_uniffi_fn.rs rename to crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_name_uniffi_fn.rs diff --git a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number.rs b/crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number.rs rename to crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number.rs diff --git a/src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number_uniffi_fn.rs b/crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number_uniffi_fn.rs rename to crates/profile/src/v100/entity/persona/persona_data/entry_kinds/persona_data_entry_phone_number_uniffi_fn.rs diff --git a/src/profile/v100/entity/persona/persona_data/mod.rs b/crates/profile/src/v100/entity/persona/persona_data/mod.rs similarity index 89% rename from src/profile/v100/entity/persona/persona_data/mod.rs rename to crates/profile/src/v100/entity/persona/persona_data/mod.rs index a2cbf9895..7d9e29b42 100644 --- a/src/profile/v100/entity/persona/persona_data/mod.rs +++ b/crates/profile/src/v100/entity/persona/persona_data/mod.rs @@ -12,6 +12,6 @@ pub use collection_of_phone_numbers::*; pub use entry_kinds::*; pub use persona_data::*; pub use persona_data_entry_id::*; -pub use persona_data_identified_collection_types::*; +pub(crate) use persona_data_identified_collection_types::*; pub use persona_data_identified_entry_types::*; pub use persona_data_uniffi_fn::*; diff --git a/src/profile/v100/entity/persona/persona_data/persona_data.rs b/crates/profile/src/v100/entity/persona/persona_data/persona_data.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/persona_data.rs rename to crates/profile/src/v100/entity/persona/persona_data/persona_data.rs diff --git a/src/profile/v100/entity/persona/persona_data/persona_data_entry_id.rs b/crates/profile/src/v100/entity/persona/persona_data/persona_data_entry_id.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/persona_data_entry_id.rs rename to crates/profile/src/v100/entity/persona/persona_data/persona_data_entry_id.rs diff --git a/src/profile/v100/entity/persona/persona_data/persona_data_identified_collection_types.rs b/crates/profile/src/v100/entity/persona/persona_data/persona_data_identified_collection_types.rs similarity index 98% rename from src/profile/v100/entity/persona/persona_data/persona_data_identified_collection_types.rs rename to crates/profile/src/v100/entity/persona/persona_data/persona_data_identified_collection_types.rs index ab02ece40..401c49a33 100644 --- a/src/profile/v100/entity/persona/persona_data/persona_data_identified_collection_types.rs +++ b/crates/profile/src/v100/entity/persona/persona_data/persona_data_identified_collection_types.rs @@ -62,10 +62,10 @@ macro_rules! declare_collection_of_identified_entry { } } - impl<'a> IntoIterator for &'a $struct_name { + impl IntoIterator for $struct_name { type Item = $id_ent_type; type IntoIter = - IdentifiedVecOfIterator<'a, $id_ent_type>; + OwnedIdentifiedVecOfIterator<$id_ent_type>; fn into_iter(self) -> Self::IntoIter { self.collection.into_iter() diff --git a/src/profile/v100/entity/persona/persona_data/persona_data_identified_entry_types.rs b/crates/profile/src/v100/entity/persona/persona_data/persona_data_identified_entry_types.rs similarity index 99% rename from src/profile/v100/entity/persona/persona_data/persona_data_identified_entry_types.rs rename to crates/profile/src/v100/entity/persona/persona_data/persona_data_identified_entry_types.rs index c576c5855..3ce25e68d 100644 --- a/src/profile/v100/entity/persona/persona_data/persona_data_identified_entry_types.rs +++ b/crates/profile/src/v100/entity/persona/persona_data/persona_data_identified_entry_types.rs @@ -54,7 +54,7 @@ macro_rules! declare_identified_entry { } } impl $struct_name { - pub(crate) fn with_id( + pub fn with_id( id: PersonaDataEntryID, value: $value_type, ) -> Self { diff --git a/src/profile/v100/entity/persona/persona_data/persona_data_uniffi_fn.rs b/crates/profile/src/v100/entity/persona/persona_data/persona_data_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_data/persona_data_uniffi_fn.rs rename to crates/profile/src/v100/entity/persona/persona_data/persona_data_uniffi_fn.rs diff --git a/src/profile/v100/entity/persona/persona_uniffi_fn.rs b/crates/profile/src/v100/entity/persona/persona_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/persona/persona_uniffi_fn.rs rename to crates/profile/src/v100/entity/persona/persona_uniffi_fn.rs diff --git a/src/profile/v100/entity_security_state/entity_security_state.rs b/crates/profile/src/v100/entity_security_state/entity_security_state.rs similarity index 100% rename from src/profile/v100/entity_security_state/entity_security_state.rs rename to crates/profile/src/v100/entity_security_state/entity_security_state.rs diff --git a/src/profile/v100/entity_security_state/entity_security_state_uniffi_fn.rs b/crates/profile/src/v100/entity_security_state/entity_security_state_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity_security_state/entity_security_state_uniffi_fn.rs rename to crates/profile/src/v100/entity_security_state/entity_security_state_uniffi_fn.rs diff --git a/src/profile/v100/entity_security_state/mod.rs b/crates/profile/src/v100/entity_security_state/mod.rs similarity index 100% rename from src/profile/v100/entity_security_state/mod.rs rename to crates/profile/src/v100/entity_security_state/mod.rs diff --git a/src/profile/v100/entity_security_state/unsecured_entity_control.rs b/crates/profile/src/v100/entity_security_state/unsecured_entity_control.rs similarity index 100% rename from src/profile/v100/entity_security_state/unsecured_entity_control.rs rename to crates/profile/src/v100/entity_security_state/unsecured_entity_control.rs diff --git a/src/profile/v100/entity_security_state/unsecured_entity_control_uniffi_fn.rs b/crates/profile/src/v100/entity_security_state/unsecured_entity_control_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity_security_state/unsecured_entity_control_uniffi_fn.rs rename to crates/profile/src/v100/entity_security_state/unsecured_entity_control_uniffi_fn.rs diff --git a/src/profile/v100/factors/factor_instance/badge_virtual_source.rs b/crates/profile/src/v100/factors/factor_instance/badge_virtual_source.rs similarity index 100% rename from src/profile/v100/factors/factor_instance/badge_virtual_source.rs rename to crates/profile/src/v100/factors/factor_instance/badge_virtual_source.rs diff --git a/src/profile/v100/factors/factor_instance/factor_instance.rs b/crates/profile/src/v100/factors/factor_instance/factor_instance.rs similarity index 100% rename from src/profile/v100/factors/factor_instance/factor_instance.rs rename to crates/profile/src/v100/factors/factor_instance/factor_instance.rs diff --git a/src/profile/v100/factors/factor_instance/factor_instance_badge.rs b/crates/profile/src/v100/factors/factor_instance/factor_instance_badge.rs similarity index 100% rename from src/profile/v100/factors/factor_instance/factor_instance_badge.rs rename to crates/profile/src/v100/factors/factor_instance/factor_instance_badge.rs diff --git a/src/profile/v100/factors/factor_instance/mod.rs b/crates/profile/src/v100/factors/factor_instance/mod.rs similarity index 100% rename from src/profile/v100/factors/factor_instance/mod.rs rename to crates/profile/src/v100/factors/factor_instance/mod.rs diff --git a/src/profile/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs b/crates/profile/src/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs similarity index 100% rename from src/profile/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs rename to crates/profile/src/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs diff --git a/src/profile/v100/factors/factor_source.rs b/crates/profile/src/v100/factors/factor_source.rs similarity index 100% rename from src/profile/v100/factors/factor_source.rs rename to crates/profile/src/v100/factors/factor_source.rs diff --git a/src/profile/v100/factors/factor_source_common.rs b/crates/profile/src/v100/factors/factor_source_common.rs similarity index 92% rename from src/profile/v100/factors/factor_source_common.rs rename to crates/profile/src/v100/factors/factor_source_common.rs index c68be852e..0e1691df3 100644 --- a/src/profile/v100/factors/factor_source_common.rs +++ b/crates/profile/src/v100/factors/factor_source_common.rs @@ -179,26 +179,6 @@ mod tests { assert!(FactorSourceCommon::new_main_bdfs().is_main_bdfs()); } - #[test] - fn new_uses_now_as_date() { - let date0 = now(); - let model = FactorSourceCommon::new( - FactorSourceCryptoParameters::default(), - [], - ); - let mut date1 = now(); - for _ in 0..10 { - // rust is too fast... lol. - date1 = now(); - } - let do_test = |d: Timestamp| { - assert!(d > date0); - assert!(d < date1); - }; - do_test(model.added_on); - do_test(model.last_used_on); - } - #[test] fn json_roundtrip() { let date = Timestamp::parse("2023-09-11T16:05:56.000Z").unwrap(); diff --git a/src/profile/v100/factors/factor_source_crypto_parameters.rs b/crates/profile/src/v100/factors/factor_source_crypto_parameters.rs similarity index 98% rename from src/profile/v100/factors/factor_source_crypto_parameters.rs rename to crates/profile/src/v100/factors/factor_source_crypto_parameters.rs index 93a84ea0a..4f02bff2f 100644 --- a/src/profile/v100/factors/factor_source_crypto_parameters.rs +++ b/crates/profile/src/v100/factors/factor_source_crypto_parameters.rs @@ -125,11 +125,7 @@ impl HasSampleValues for SupportedCurves { #[cfg(test)] mod tests { use super::*; - use uniffi::{ - check_remaining, - deps::bytes::{Buf, BufMut}, - metadata, Lift, Lower, LowerReturn, MetadataBuffer, RustBuffer, - }; + use uniffi::{deps::bytes::BufMut, Lift, RustBuffer}; #[allow(clippy::upper_case_acronyms)] type SUT = FactorSourceCryptoParameters; diff --git a/src/profile/v100/factors/factor_source_crypto_parameters_uniffi_fn.rs b/crates/profile/src/v100/factors/factor_source_crypto_parameters_uniffi_fn.rs similarity index 100% rename from src/profile/v100/factors/factor_source_crypto_parameters_uniffi_fn.rs rename to crates/profile/src/v100/factors/factor_source_crypto_parameters_uniffi_fn.rs diff --git a/src/profile/v100/factors/factor_source_flag.rs b/crates/profile/src/v100/factors/factor_source_flag.rs similarity index 100% rename from src/profile/v100/factors/factor_source_flag.rs rename to crates/profile/src/v100/factors/factor_source_flag.rs diff --git a/src/profile/v100/factors/factor_source_id.rs b/crates/profile/src/v100/factors/factor_source_id.rs similarity index 100% rename from src/profile/v100/factors/factor_source_id.rs rename to crates/profile/src/v100/factors/factor_source_id.rs diff --git a/src/profile/v100/factors/factor_source_id_from_address.rs b/crates/profile/src/v100/factors/factor_source_id_from_address.rs similarity index 100% rename from src/profile/v100/factors/factor_source_id_from_address.rs rename to crates/profile/src/v100/factors/factor_source_id_from_address.rs diff --git a/src/profile/v100/factors/factor_source_id_from_address_uniffi_fn.rs b/crates/profile/src/v100/factors/factor_source_id_from_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/factors/factor_source_id_from_address_uniffi_fn.rs rename to crates/profile/src/v100/factors/factor_source_id_from_address_uniffi_fn.rs diff --git a/src/profile/v100/factors/factor_source_id_from_hash.rs b/crates/profile/src/v100/factors/factor_source_id_from_hash.rs similarity index 99% rename from src/profile/v100/factors/factor_source_id_from_hash.rs rename to crates/profile/src/v100/factors/factor_source_id_from_hash.rs index fec9393f7..74bf6979b 100644 --- a/src/profile/v100/factors/factor_source_id_from_hash.rs +++ b/crates/profile/src/v100/factors/factor_source_id_from_hash.rs @@ -1,5 +1,4 @@ use crate::prelude::*; -use radix_common::crypto::{blake2b_256_hash, Hash}; /// FactorSourceID from the blake2b hash of the special HD public key derived at `CAP26::GetID`, /// for a certain `FactorSourceKind` @@ -10,7 +9,7 @@ use radix_common::crypto::{blake2b_256_hash, Hash}; Deserialize, PartialEq, Eq, - Hash, + std::hash::Hash, derive_more::Display, derive_more::Debug, uniffi::Record, diff --git a/src/profile/v100/factors/factor_source_id_from_hash_uniffi_fn.rs b/crates/profile/src/v100/factors/factor_source_id_from_hash_uniffi_fn.rs similarity index 100% rename from src/profile/v100/factors/factor_source_id_from_hash_uniffi_fn.rs rename to crates/profile/src/v100/factors/factor_source_id_from_hash_uniffi_fn.rs diff --git a/src/profile/v100/factors/factor_source_id_uniffi_fn.rs b/crates/profile/src/v100/factors/factor_source_id_uniffi_fn.rs similarity index 100% rename from src/profile/v100/factors/factor_source_id_uniffi_fn.rs rename to crates/profile/src/v100/factors/factor_source_id_uniffi_fn.rs diff --git a/src/profile/v100/factors/factor_source_kind.rs b/crates/profile/src/v100/factors/factor_source_kind.rs similarity index 100% rename from src/profile/v100/factors/factor_source_kind.rs rename to crates/profile/src/v100/factors/factor_source_kind.rs diff --git a/src/profile/v100/factors/factor_source_kind_uniffi_fn.rs b/crates/profile/src/v100/factors/factor_source_kind_uniffi_fn.rs similarity index 100% rename from src/profile/v100/factors/factor_source_kind_uniffi_fn.rs rename to crates/profile/src/v100/factors/factor_source_kind_uniffi_fn.rs diff --git a/src/profile/v100/factors/factor_source_uniffi_fn.rs b/crates/profile/src/v100/factors/factor_source_uniffi_fn.rs similarity index 100% rename from src/profile/v100/factors/factor_source_uniffi_fn.rs rename to crates/profile/src/v100/factors/factor_source_uniffi_fn.rs diff --git a/src/profile/v100/factors/factor_sources/device_factor_source/device_factor_source.rs b/crates/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs similarity index 90% rename from src/profile/v100/factors/factor_sources/device_factor_source/device_factor_source.rs rename to crates/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs index 31d20930a..7aad0288b 100644 --- a/src/profile/v100/factors/factor_sources/device_factor_source/device_factor_source.rs +++ b/crates/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs @@ -75,40 +75,32 @@ impl DeviceFactorSource { pub fn babylon( is_main: bool, mnemonic_with_passphrase: &MnemonicWithPassphrase, - wallet_client_model: WalletClientModel, + device_info: &DeviceInfo, ) -> Self { let id = FactorSourceIDFromHash::from_mnemonic_with_passphrase( FactorSourceKind::Device, mnemonic_with_passphrase, ); - - Self::new( - id, - FactorSourceCommon::new_bdfs(is_main), - DeviceFactorSourceHint::unknown_model_of_client( - mnemonic_with_passphrase.mnemonic.word_count, - wallet_client_model, - ), - ) + let hint = DeviceFactorSourceHint::with_info( + device_info, + mnemonic_with_passphrase.mnemonic.word_count, + ); + Self::new(id, FactorSourceCommon::new_bdfs(is_main), hint) } pub fn olympia( mnemonic_with_passphrase: &MnemonicWithPassphrase, - wallet_client_model: WalletClientModel, + device_info: &DeviceInfo, ) -> Self { let id = FactorSourceIDFromHash::from_mnemonic_with_passphrase( FactorSourceKind::Device, mnemonic_with_passphrase, ); - - Self::new( - id, - FactorSourceCommon::new_olympia(), - DeviceFactorSourceHint::unknown_model_of_client( - mnemonic_with_passphrase.mnemonic.word_count, - wallet_client_model, - ), - ) + let hint = DeviceFactorSourceHint::with_info( + device_info, + mnemonic_with_passphrase.mnemonic.word_count, + ); + Self::new(id, FactorSourceCommon::new_olympia(), hint) } /// Checks if its Main Babylon Device Factor Source (BDFS). @@ -175,7 +167,7 @@ mod tests { assert!(DeviceFactorSource::babylon( true, &MnemonicWithPassphrase::sample(), - WalletClientModel::sample() + &DeviceInfo::sample() ) .is_main_bdfs()); } diff --git a/crates/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs b/crates/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs new file mode 100644 index 000000000..7f02fa8b7 --- /dev/null +++ b/crates/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs @@ -0,0 +1,197 @@ +use crate::prelude::*; + +/// Properties describing a DeviceFactorSource to help user disambiguate between +/// it and another one. +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + PartialEq, + Eq, + Hash, + derive_more::Display, + uniffi::Record, +)] +#[serde(rename_all = "camelCase")] +#[display("{name} {model}")] +pub struct DeviceFactorSourceHint { + /// "iPhone RED" + pub name: String, + + /// "iPhone SE 2nd gen" + pub model: String, + + /// The number of words in the mnemonic of a DeviceFactorSource, according to the BIP39 + /// standard, a multiple of 3, from 12 to 24 words. + pub mnemonic_word_count: BIP39WordCount, + + /// The **last known** version of the device's operating system, e.g. "iOS 17.4.1". + /// + /// It is possible that the host device has been updated to a new + /// version than recorded here, but Sargon or host clients might + /// just not have updated this value here. + /// + /// MUST be optional since this was added on 2024-05-03 and + /// was not present in earlier version of wallet (pre 1.6.0). + pub system_version: Option, + + /// The **last known** version of the host app, for example the Radix iOS Wallet version - e.g. "1.6.1" + /// + /// It is possible that the host device has been updated to a new + /// version than recorded here, but Sargon or host clients might + /// just not have updated this value here. + /// + /// MUST be optional since this was added on 2024-05-03 and + /// was not present in earlier version of wallet (pre 1.6.0). + pub host_app_version: Option, + + /// The vendor of the device host, e.g. "Apple" or "Samsung". + /// + /// MUST be optional since this was added on 2024-05-03 and + /// was not present in earlier version of wallet (pre 1.6.0). + pub host_vendor: Option, +} + +impl DeviceFactorSourceHint { + /// Instantiates a new DeviceFactorSourceHint from the specified name, model, + /// system version, app version and mnemonic word count. + pub fn new( + name: impl AsRef, + model: impl AsRef, + system_version: impl Into>, + host_app_version: impl Into>, + host_vendor: impl Into>, + word_count: BIP39WordCount, + ) -> Self { + Self { + name: name.as_ref().to_owned(), + model: model.as_ref().to_owned(), + system_version: system_version.into(), + host_app_version: host_app_version.into(), + host_vendor: host_vendor.into(), + mnemonic_word_count: word_count, + } + } + + pub fn with_info( + device_info: &DeviceInfo, + word_count: BIP39WordCount, + ) -> Self { + Self::new( + device_info.description.name.clone(), + device_info.description.model.clone(), + device_info.system_version.clone(), + device_info.host_app_version.clone(), + device_info.host_vendor.clone(), + word_count, + ) + } +} + +impl HasSampleValues for DeviceFactorSourceHint { + /// A sample used to facilitate unit tests. + fn sample() -> Self { + Self::new( + "Unknown Name", + "iPhone", + None, + None, + None, + BIP39WordCount::TwentyFour, + ) + } + + fn sample_other() -> Self { + Self::new( + "Android", + "Samsung Galaxy S23 Ultra", + None, + None, + None, + BIP39WordCount::Twelve, + ) + } +} + +impl DeviceFactorSourceHint { + /// A sample used to facilitate unit tests. + pub fn sample_iphone_unknown() -> Self { + Self::new( + "Unknown Name", + "iPhone", + None, + None, + None, + BIP39WordCount::TwentyFour, + ) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = DeviceFactorSourceHint; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn get_word_count() { + assert_eq!( + SUT::sample().mnemonic_word_count, + BIP39WordCount::TwentyFour + ); + } + + #[test] + fn json() { + let model = SUT::sample_iphone_unknown(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "name": "Unknown Name", + "model": "iPhone", + "mnemonicWordCount": 24 + } + "#, + ) + } + + #[test] + fn json_app_version_and_system_version_set() { + let sut = SUT::new( + "My precious", + "iPhone 15 Pro", + "17.4.1".to_owned(), + "1.6.0".to_owned(), + "Apple".to_owned(), + BIP39WordCount::TwentyFour, + ); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "name": "My precious", + "model": "iPhone 15 Pro", + "systemVersion": "17.4.1", + "hostAppVersion": "1.6.0", + "hostVendor": "Apple", + "mnemonicWordCount": 24 + } + "#, + ) + } +} diff --git a/src/profile/v100/factors/factor_sources/device_factor_source/device_factor_source_uniffi_fn.rs b/crates/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_uniffi_fn.rs similarity index 84% rename from src/profile/v100/factors/factor_sources/device_factor_source/device_factor_source_uniffi_fn.rs rename to crates/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_uniffi_fn.rs index 3a851715f..6e7a7742f 100644 --- a/src/profile/v100/factors/factor_sources/device_factor_source/device_factor_source_uniffi_fn.rs +++ b/crates/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_uniffi_fn.rs @@ -14,21 +14,17 @@ pub fn new_device_factor_source_sample_other() -> DeviceFactorSource { pub fn new_device_factor_source_babylon( is_main: bool, mnemonic_with_passphrase: &MnemonicWithPassphrase, - wallet_client_model: WalletClientModel, + device_info: &DeviceInfo, ) -> DeviceFactorSource { - DeviceFactorSource::babylon( - is_main, - mnemonic_with_passphrase, - wallet_client_model, - ) + DeviceFactorSource::babylon(is_main, mnemonic_with_passphrase, device_info) } #[uniffi::export] pub fn new_device_factor_source_olympia( mnemonic_with_passphrase: &MnemonicWithPassphrase, - wallet_client_model: WalletClientModel, + device_info: &DeviceInfo, ) -> DeviceFactorSource { - DeviceFactorSource::olympia(mnemonic_with_passphrase, wallet_client_model) + DeviceFactorSource::olympia(mnemonic_with_passphrase, device_info) } #[uniffi::export] @@ -49,7 +45,7 @@ mod tests { fn test_new_olympia() { let olympia = new_device_factor_source_olympia( &MnemonicWithPassphrase::sample(), - WalletClientModel::Iphone, + &DeviceInfo::sample(), ); assert!(factor_source_supports_olympia(&olympia.clone().into())); @@ -61,7 +57,7 @@ mod tests { let babylon = new_device_factor_source_babylon( true, &MnemonicWithPassphrase::sample(), - WalletClientModel::Iphone, + &DeviceInfo::sample(), ); assert!(factor_source_supports_babylon(&babylon.clone().into())); @@ -73,7 +69,7 @@ mod tests { let babylon = new_device_factor_source_babylon( false, &MnemonicWithPassphrase::sample(), - WalletClientModel::Iphone, + &DeviceInfo::sample(), ); assert!(!device_factor_source_is_main_bdfs(&babylon)); @@ -84,7 +80,7 @@ mod tests { let babylon = new_device_factor_source_babylon( true, &MnemonicWithPassphrase::sample(), - WalletClientModel::Iphone, + &DeviceInfo::sample(), ); assert!(device_factor_source_is_main_bdfs(&babylon)); diff --git a/src/profile/v100/factors/factor_sources/device_factor_source/mod.rs b/crates/profile/src/v100/factors/factor_sources/device_factor_source/mod.rs similarity index 100% rename from src/profile/v100/factors/factor_sources/device_factor_source/mod.rs rename to crates/profile/src/v100/factors/factor_sources/device_factor_source/mod.rs diff --git a/src/profile/v100/factors/factor_sources/device_factor_source/wallet_client_model.rs b/crates/profile/src/v100/factors/factor_sources/device_factor_source/wallet_client_model.rs similarity index 100% rename from src/profile/v100/factors/factor_sources/device_factor_source/wallet_client_model.rs rename to crates/profile/src/v100/factors/factor_sources/device_factor_source/wallet_client_model.rs diff --git a/src/profile/v100/factors/factor_sources/factor_sources.rs b/crates/profile/src/v100/factors/factor_sources/factor_sources.rs similarity index 97% rename from src/profile/v100/factors/factor_sources/factor_sources.rs rename to crates/profile/src/v100/factors/factor_sources/factor_sources.rs index 75eed3844..7677ce857 100644 --- a/src/profile/v100/factors/factor_sources/factor_sources.rs +++ b/crates/profile/src/v100/factors/factor_sources/factor_sources.rs @@ -34,11 +34,7 @@ impl HasSampleValues for FactorSources { mod tests { use super::*; - use uniffi::{ - check_remaining, - deps::bytes::{Buf, BufMut}, - metadata, Lift, Lower, LowerReturn, MetadataBuffer, RustBuffer, - }; + use uniffi::{deps::bytes::BufMut, Lift, RustBuffer}; #[allow(clippy::upper_case_acronyms)] type SUT = FactorSources; diff --git a/src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs b/crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs similarity index 100% rename from src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs rename to crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs diff --git a/src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source_uniffi_fn.rs b/crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source_uniffi_fn.rs similarity index 100% rename from src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source_uniffi_fn.rs rename to crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source_uniffi_fn.rs diff --git a/src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_hint.rs b/crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_hint.rs similarity index 100% rename from src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_hint.rs rename to crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_hint.rs diff --git a/src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model.rs b/crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model.rs similarity index 100% rename from src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model.rs rename to crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model.rs diff --git a/src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model_uniffi_fn.rs b/crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model_uniffi_fn.rs similarity index 100% rename from src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model_uniffi_fn.rs rename to crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model_uniffi_fn.rs diff --git a/src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/mod.rs b/crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/mod.rs similarity index 100% rename from src/profile/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/mod.rs rename to crates/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/mod.rs diff --git a/src/profile/v100/factors/factor_sources/mod.rs b/crates/profile/src/v100/factors/factor_sources/mod.rs similarity index 100% rename from src/profile/v100/factors/factor_sources/mod.rs rename to crates/profile/src/v100/factors/factor_sources/mod.rs diff --git a/src/profile/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source.rs b/crates/profile/src/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source.rs similarity index 67% rename from src/profile/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source.rs rename to crates/profile/src/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source.rs index 457a08a82..5803475ab 100644 --- a/src/profile/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source.rs +++ b/crates/profile/src/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source.rs @@ -28,54 +28,52 @@ impl PrivateHierarchicalDeterministicFactorSource { pub fn new_olympia_with_mnemonic_with_passphrase( mnemonic_with_passphrase: MnemonicWithPassphrase, - wallet_client_model: WalletClientModel, + device_info: &DeviceInfo, ) -> Self { - let device_factor_source = DeviceFactorSource::olympia( - &mnemonic_with_passphrase, - wallet_client_model, - ); + let device_factor_source = + DeviceFactorSource::olympia(&mnemonic_with_passphrase, device_info); Self::new(mnemonic_with_passphrase, device_factor_source) } pub fn new_babylon_with_mnemonic_with_passphrase( is_main: bool, mnemonic_with_passphrase: MnemonicWithPassphrase, - wallet_client_model: WalletClientModel, + device_info: &DeviceInfo, ) -> Self { let bdfs = DeviceFactorSource::babylon( is_main, &mnemonic_with_passphrase, - wallet_client_model, + device_info, ); Self::new(mnemonic_with_passphrase, bdfs) } pub fn new_babylon_with_entropy( is_main: bool, - entropy: BIP39Entropy, + entropy: NonEmptyMax32Bytes, passphrase: BIP39Passphrase, - wallet_client_model: WalletClientModel, - ) -> Self { - let mnemonic = Mnemonic::from_entropy(entropy); + device_info: &DeviceInfo, + ) -> Result { + let mnemonic = Mnemonic::from_entropy(entropy)?; let mnemonic_with_passphrase = MnemonicWithPassphrase::with_passphrase(mnemonic, passphrase); - Self::new_babylon_with_mnemonic_with_passphrase( + Ok(Self::new_babylon_with_mnemonic_with_passphrase( is_main, mnemonic_with_passphrase, - wallet_client_model, - ) + device_info, + )) } pub fn generate_new_babylon( is_main: bool, - wallet_client_model: WalletClientModel, + device_info: &DeviceInfo, ) -> Self { let mnemonic = Mnemonic::generate_new(); let mnemonic_with_passphrase = MnemonicWithPassphrase::new(mnemonic); Self::new_babylon_with_mnemonic_with_passphrase( is_main, mnemonic_with_passphrase, - wallet_client_model, + device_info, ) } } @@ -89,16 +87,41 @@ impl PrivateHierarchicalDeterministicFactorSource { where T: IsEntityPath + Clone, { - let path = T::new(network_id, CAP26KeyKind::TransactionSigning, index); + self.derive_entity_creation_factor_instances(network_id, [index]) + .into_iter() + .last() + .expect("Should have created one factor instance") + } + + pub fn derive_entity_creation_factor_instances( + &self, + network_id: NetworkID, + indices: impl IntoIterator, + ) -> Vec> + where + T: IsEntityPath + Clone, + { + let paths = indices + .into_iter() + .map(|i| T::new(network_id, CAP26KeyKind::TransactionSigning, i)); + let mut seed = self.mnemonic_with_passphrase.to_seed(); - let hd_private_key = seed.derive_private_key(&path); - let hd_factor_instance = HierarchicalDeterministicFactorInstance::new( - self.factor_source.id, - hd_private_key.public_key(), - ); + let instances = paths + .map(|p| { + let hd_private_key = seed.derive_private_key(&p); + let hd_factor_instance = + HierarchicalDeterministicFactorInstance::new( + self.factor_source.id, + hd_private_key.public_key(), + ); + // TODO: zeroize `hd_private_key` when `HierarchicalDeterministicPrivateKey` implement Zeroize... + HDFactorInstanceTransactionSigning::new(hd_factor_instance) + .unwrap() + }) + .collect_vec(); + seed.zeroize(); - // TODO: zeroize `hd_private_key` when `HierarchicalDeterministicPrivateKey` implement Zeroize... - HDFactorInstanceTransactionSigning::new(hd_factor_instance).unwrap() + instances } } @@ -144,9 +167,7 @@ mod tests { fn hash() { let n = 100; let set = (0..n) - .map(|_| { - SUT::generate_new_babylon(true, WalletClientModel::Unknown) - }) + .map(|_| SUT::generate_new_babylon(true, &DeviceInfo::sample())) .collect::>(); assert_eq!(set.len(), n); } diff --git a/src/profile/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source_uniffi_fn.rs b/crates/profile/src/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source_uniffi_fn.rs similarity index 79% rename from src/profile/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source_uniffi_fn.rs rename to crates/profile/src/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source_uniffi_fn.rs index 913dc33e6..ab648a67b 100644 --- a/src/profile/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source_uniffi_fn.rs +++ b/crates/profile/src/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source_uniffi_fn.rs @@ -4,33 +4,31 @@ use crate::prelude::*; pub fn new_private_hd_factor_source_babylon( is_main: bool, entropy: NonEmptyMax32Bytes, - wallet_client_model: WalletClientModel, + device_info: &DeviceInfo, ) -> Result { - BIP39Entropy::try_from(entropy).map(|entropy| { - PrivateHierarchicalDeterministicFactorSource::new_babylon_with_entropy( - is_main, - entropy, - BIP39Passphrase::default(), - wallet_client_model, - ) - }) + PrivateHierarchicalDeterministicFactorSource::new_babylon_with_entropy( + is_main, + entropy, + BIP39Passphrase::default(), + device_info, + ) } #[uniffi::export] pub fn new_private_hd_factor_source_babylon_from_mnemonic_with_passphrase( is_main: bool, mnemonic_with_passphrase: MnemonicWithPassphrase, - wallet_client_model: WalletClientModel, + device_info: &DeviceInfo, ) -> PrivateHierarchicalDeterministicFactorSource { - PrivateHierarchicalDeterministicFactorSource::new_babylon_with_mnemonic_with_passphrase(is_main, mnemonic_with_passphrase, wallet_client_model) + PrivateHierarchicalDeterministicFactorSource::new_babylon_with_mnemonic_with_passphrase(is_main, mnemonic_with_passphrase, device_info) } #[uniffi::export] pub fn new_private_hd_factor_source_olympia_from_mnemonic_with_passphrase( mnemonic_with_passphrase: MnemonicWithPassphrase, - wallet_client_model: WalletClientModel, + device_info: &DeviceInfo, ) -> PrivateHierarchicalDeterministicFactorSource { - PrivateHierarchicalDeterministicFactorSource::new_olympia_with_mnemonic_with_passphrase(mnemonic_with_passphrase, wallet_client_model) + PrivateHierarchicalDeterministicFactorSource::new_olympia_with_mnemonic_with_passphrase(mnemonic_with_passphrase, device_info) } #[uniffi::export] @@ -71,8 +69,8 @@ mod tests { fn new_uses_empty_bip39_passphrase() { let private: SUT = new_private_hd_factor_source_babylon( true, - Entropy32Bytes::new([0xff; 32]).into(), - WalletClientModel::Unknown, + NonEmptyMax32Bytes::from([0xff; 32]), + &DeviceInfo::sample(), ) .unwrap(); assert_eq!(private.mnemonic_with_passphrase.passphrase.0, ""); @@ -85,7 +83,7 @@ mod tests { new_private_hd_factor_source_babylon_from_mnemonic_with_passphrase( true, MnemonicWithPassphrase::sample(), - WalletClientModel::Android, + &DeviceInfo::sample(), ); assert!(&sut.factor_source.supports_babylon()); assert!(!&sut.factor_source.supports_olympia()); @@ -98,7 +96,7 @@ mod tests { new_private_hd_factor_source_babylon_from_mnemonic_with_passphrase( true, MnemonicWithPassphrase::sample(), - WalletClientModel::Android, + &DeviceInfo::sample(), ); assert!(sut.factor_source.is_main_bdfs()); } @@ -109,7 +107,7 @@ mod tests { let sut = new_private_hd_factor_source_olympia_from_mnemonic_with_passphrase( MnemonicWithPassphrase::sample(), - WalletClientModel::Android, + &DeviceInfo::sample(), ); assert!(&sut.factor_source.supports_olympia()); assert!(!&sut.factor_source.supports_babylon()); diff --git a/src/profile/v100/factors/hd_transaction_signing_factor_instance.rs b/crates/profile/src/v100/factors/hd_transaction_signing_factor_instance.rs similarity index 100% rename from src/profile/v100/factors/hd_transaction_signing_factor_instance.rs rename to crates/profile/src/v100/factors/hd_transaction_signing_factor_instance.rs diff --git a/src/profile/v100/factors/hierarchical_deterministic_factor_instance.rs b/crates/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs similarity index 100% rename from src/profile/v100/factors/hierarchical_deterministic_factor_instance.rs rename to crates/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs diff --git a/src/profile/v100/factors/hierarchical_deterministic_factor_instance_uniffi_fn.rs b/crates/profile/src/v100/factors/hierarchical_deterministic_factor_instance_uniffi_fn.rs similarity index 100% rename from src/profile/v100/factors/hierarchical_deterministic_factor_instance_uniffi_fn.rs rename to crates/profile/src/v100/factors/hierarchical_deterministic_factor_instance_uniffi_fn.rs diff --git a/src/profile/v100/factors/is_factor_source.rs b/crates/profile/src/v100/factors/is_factor_source.rs similarity index 100% rename from src/profile/v100/factors/is_factor_source.rs rename to crates/profile/src/v100/factors/is_factor_source.rs diff --git a/src/profile/v100/factors/mod.rs b/crates/profile/src/v100/factors/mod.rs similarity index 100% rename from src/profile/v100/factors/mod.rs rename to crates/profile/src/v100/factors/mod.rs diff --git a/src/profile/v100/header/content_hint.rs b/crates/profile/src/v100/header/content_hint.rs similarity index 99% rename from src/profile/v100/header/content_hint.rs rename to crates/profile/src/v100/header/content_hint.rs index 885b93fc8..6132ba7ef 100644 --- a/src/profile/v100/header/content_hint.rs +++ b/crates/profile/src/v100/header/content_hint.rs @@ -62,7 +62,7 @@ impl ContentHint { } /// Instantiates a new `ContentHint` with all counters equal `count`. - pub(crate) fn all(count: usize) -> Self { + pub fn all(count: usize) -> Self { Self::with_counters(count, count, count) } diff --git a/crates/profile/src/v100/header/device_id.rs b/crates/profile/src/v100/header/device_id.rs new file mode 100644 index 000000000..bbe67c2bf --- /dev/null +++ b/crates/profile/src/v100/header/device_id.rs @@ -0,0 +1,85 @@ +use crate::prelude::*; + +/// A stable and globally unique identifier of a device, +/// e.g. an Android phone. +#[derive( + Serialize, + Deserialize, + Debug, + Copy, + derive_more::Display, + Clone, + PartialEq, + Eq, + Hash, +)] +#[serde(transparent)] +pub struct DeviceID(pub Uuid); + +uniffi::custom_newtype!(DeviceID, Uuid); + +impl DeviceID { + pub fn generate_new() -> Self { + Self(id()) + } +} + +impl FromStr for DeviceID { + type Err = CommonError; + fn from_str(s: &str) -> Result { + Uuid::from_str(s).map(DeviceID).map_err(|_| { + CommonError::InvalidDeviceID { + bad_value: s.to_owned(), + } + }) + } +} + +impl HasSampleValues for DeviceID { + fn sample() -> Self { + DeviceID(Uuid::sample()) + } + + fn sample_other() -> Self { + DeviceID(Uuid::sample_other()) + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = DeviceID; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn from_str_invalid() { + assert_eq!( + "bad".parse::(), + Err(CommonError::InvalidDeviceID { + bad_value: "bad".to_owned() + }) + ); + } + + #[test] + fn from_upper_case_is_ok() { + assert!(SUT::from_str("66F07CA2-A9D9-49E5-8152-77ACA3D1DD74").is_ok()) + } + + #[test] + fn generate_new_is_unique() { + assert_ne!(SUT::generate_new(), SUT::generate_new()); + } +} diff --git a/crates/profile/src/v100/header/device_info.rs b/crates/profile/src/v100/header/device_info.rs new file mode 100644 index 000000000..abb87a9e8 --- /dev/null +++ b/crates/profile/src/v100/header/device_info.rs @@ -0,0 +1,345 @@ +use crate::prelude::*; + +/// A short summary of a device the Profile is being used +/// on, typically an iPhone or an Android phone. +#[derive( + Serialize, + Deserialize, + Clone, + Debug, + PartialEq, + Eq, + Hash, + derive_more::Display, + uniffi::Record, +)] +#[display("{} | created: {} | #{}", description, self.date.date(), id.to_string())] +pub struct DeviceInfo { + /// A best effort stable and unique identifier of this + /// device. + /// + /// Apple has made it so that iOS devices cannot + /// query iOS for a unique identifier of the device, thus + /// the iOS team has made their own impl of a best effort + /// stable identifier. + pub id: DeviceID, + + /// The date this description of the device was made, might + /// be equal to when the app was first ever launched on the + /// device. + pub date: Timestamp, + + /// A short description of the device, we devices should + /// read the device model and a given name from the device + /// if they are able to. + pub description: DeviceInfoDescription, + + /// The **last known** version of the device's operating system, e.g. "iOS 17.4.1". + /// + /// It is possible that the host device has been updated to a new + /// version than recorded here, but Sargon or host clients might + /// just not have updated this value here. + /// + /// MUST be optional since this was added on 2024-05-03 and + /// was not present in earlier version of wallet (pre 1.6.0). + pub system_version: Option, + + /// The **last known** version of the host app, for example the Radix iOS Wallet version - e.g. "1.6.1" + /// + /// It is possible that the host device has been updated to a new + /// version than recorded here, but Sargon or host clients might + /// just not have updated this value here. + /// + /// MUST be optional since this was added on 2024-05-03 and + /// was not present in earlier version of wallet (pre 1.6.0). + pub host_app_version: Option, + + /// The vendor of the host client, e.g. "Apple" for iPhone clients, + /// or "Samsung" for Android clients. + /// + /// MUST be optional since this was added on 2024-05-16 and + /// was not present in earlier version of wallet (pre 1.6.0). + pub host_vendor: Option, +} + +impl DeviceInfo { + /// Instantiates a new `DeviceInfo` with `id`, `date` and `description`. + pub fn new( + id: DeviceID, + date: Timestamp, + description: DeviceInfoDescription, + system_version: impl AsRef, + host_app_version: impl AsRef, + host_vendor: impl AsRef, + ) -> Self { + Self { + id, + date, + description, + system_version: Some(system_version.as_ref().to_owned()), + host_app_version: Some(host_app_version.as_ref().to_owned()), + host_vendor: Some(host_vendor.as_ref().to_owned()), + } + } + + /// Instantiates a new `DeviceInfo` with all needed details, + /// formatting a `description` from host name and host model. + pub fn with_details( + id: impl Into>, + name: impl AsRef, + model: impl AsRef, + system_version: impl AsRef, + host_app_version: impl AsRef, + host_vendor: impl AsRef, + ) -> Self { + Self::new( + id.into().unwrap_or(DeviceID::generate_new()), + now(), + DeviceInfoDescription::new(name, model), + system_version, + host_app_version, + host_vendor, + ) + } +} + +#[cfg(test)] +impl DeviceInfo { + pub fn new_unknown() -> Self { + Self::with_details( + None, "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", + ) + } +} + +impl HasSampleValues for DeviceInfo { + fn sample() -> Self { + Self { + id: DeviceID::from_str("66F07CA2-A9D9-49E5-8152-77ACA3D1DD74") + .unwrap(), + date: Timestamp::sample(), + description: DeviceInfoDescription { + name: "iPhone".to_owned(), + model: "iPhone".to_owned(), + }, + system_version: None, + host_app_version: None, + host_vendor: None, + } + } + + fn sample_other() -> Self { + Self { + id: DeviceID::from_str("f07ca662-d9a9-9e45-1582-aca773d174dd") + .unwrap(), + date: Timestamp::sample_other(), + description: DeviceInfoDescription { + name: "Android".to_owned(), + model: "Android".to_owned(), + }, + system_version: None, + host_app_version: None, + host_vendor: None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = DeviceInfo; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn with_details() { + assert_eq!( + SUT::with_details( + None, + "My precious", + "iPhone SE 2nd gen", + "iOS 17.4.1", + "1.6.4", + "Apple" + ) + .description + .to_string(), + "My precious (iPhone SE 2nd gen)" + ); + } + + #[test] + fn display() { + let id_str = "12345678-bbbb-cccc-dddd-abcd12345678"; + let id = DeviceID::from_str(id_str).unwrap(); + let sut = SUT::new( + id, + Timestamp::parse("2023-09-11T16:05:56Z").unwrap(), + DeviceInfoDescription::new("Foo", "Unknown"), + "Unknown", + "Unknown", + "Unknown", + ); + pretty_assertions::assert_eq!( + format!("{sut}"), + format!("Foo (Unknown) | created: 2023-09-11 | #{}", id_str) + ) + } + + #[test] + fn id_is_unique() { + let n = 20; + let ids = (0..n) + .map(|_| SUT::new_unknown()) + .map(|d| d.id) + .collect::>(); + assert_eq!(ids.len(), n); + } + + #[test] + fn date_is_now() { + assert!(SUT::new_unknown().date.year() >= 2023); + } + + #[test] + fn can_parse_iso8601_json_without_milliseconds_precision() { + let str = r#" + { + "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", + "date": "2023-09-11T16:05:56Z", + "description": "iPhone" + } + "#; + let model = serde_json::from_str::(str).unwrap(); + assert_eq!(model.date.day(), 11); + let json = serde_json::to_string(&model).unwrap(); + assert!(json.contains("56.000Z")); + } + + #[test] + fn json_nanoseconds_precision() { + assert_json_roundtrip(&SUT::new_unknown()); + } + + #[test] + fn json_roundtrip() { + let model = SUT::sample(); + assert_eq_after_json_roundtrip( + &model, + // The JSON string literal below contains `.000` ISO8601 + // milliseconds which is not set on the sample + r#" + { + "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", + "date": "2023-09-11T16:05:56.000Z", + "description": { "name": "iPhone", "model": "iPhone" } + } + "#, + ); + assert_json_roundtrip(&model); + assert_ne_after_json_roundtrip( + &model, + r#" + { + "id": "00000000-0000-0000-0000-000000000000", + "date": "1970-01-01T12:34:56Z", + "description": { "name": "Nokia", "model": "3310" } + } + "#, + ); + } + + #[test] + fn json_roundtrip_with_system_and_app_version() { + let sut = SUT::new( + DeviceID::from_str("66F07CA2-A9D9-49E5-8152-77ACA3D1DD74").unwrap(), + Timestamp::parse("2023-09-11T16:05:56Z").unwrap(), + DeviceInfoDescription::new("My nice iPhone", "iPhone 15 Pro"), + "17.4.1", + "1.6.0", + "Apple", + ); + assert_eq_after_json_roundtrip( + &sut, + // The JSON string literal below contains `.000` ISO8601 + // milliseconds which is not set on the sample + r#" + { + "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", + "date": "2023-09-11T16:05:56.000Z", + "description": { + "name": "My nice iPhone", + "model": "iPhone 15 Pro" + }, + "system_version": "17.4.1", + "host_app_version": "1.6.0", + "host_vendor": "Apple" + } + "#, + ) + } + + #[test] + fn invalid_json() { + assert_json_fails::( + r#" + { + "id": "invalid-uuid", + "date": "1970-01-01T12:34:56.000Z", + "description": "iPhone" + } + "#, + ); + + assert_json_fails::( + r#" + { + "id": "00000000-0000-0000-0000-000000000000", + "date": "invalid-date", + "description": "iPhone" + } + "#, + ); + + assert_json_fails::( + r#" + { + "missing_key": "id", + "date": "1970-01-01T12:34:56.000Z", + "description": "iPhone" + } + "#, + ); + + assert_json_fails::( + r#" + { + "id": "00000000-0000-0000-0000-000000000000", + "missing_key": "date", + "description": "iPhone" + } + "#, + ); + + assert_json_fails::( + r#" + { + "id": "00000000-0000-0000-0000-000000000000", + "date": "1970-01-01T12:34:56.000Z", + "missing_key": "description" + } + "#, + ); + } +} diff --git a/crates/profile/src/v100/header/device_info_description.rs b/crates/profile/src/v100/header/device_info_description.rs new file mode 100644 index 000000000..43670b8d2 --- /dev/null +++ b/crates/profile/src/v100/header/device_info_description.rs @@ -0,0 +1,153 @@ +use crate::prelude::*; + +/// A name and model of a host device. +/// +/// This used to be a String only in Pre 1.6.0 wallets, so +/// we have a custom Deserialize impl of it. +#[derive( + Serialize, + Clone, + Debug, + PartialEq, + Eq, + Hash, + derive_more::Display, + uniffi::Record, +)] +#[display("{name} ({model})")] +pub struct DeviceInfoDescription { + /// Host device name, e.g. "My Precious" + pub name: String, + + /// Host device model, e.g. "iPhone 15 Pro" + pub model: String, +} + +impl DeviceInfoDescription { + pub fn new(name: impl AsRef, model: impl AsRef) -> Self { + Self { + name: name.as_ref().to_owned(), + model: model.as_ref().to_owned(), + } + } +} + +impl<'de> Deserialize<'de> for DeviceInfoDescription { + fn deserialize>( + deserializer: D, + ) -> Result { + #[derive(Deserialize, Serialize)] + struct NewFormat { + pub name: String, + pub model: String, + } + + #[derive(Deserialize, Serialize)] + #[serde(untagged)] + enum Wrapper { + NewFormat(NewFormat), + OldFormat(String), + } + + match Wrapper::deserialize(deserializer)? { + Wrapper::NewFormat(new_format) => Ok(Self { + name: new_format.name, + model: new_format.model, + }), + Wrapper::OldFormat(description) => { + // Swift used: "\(model) (\(name))" + let swift_name_suffix = " (iPhone)"; + if description.ends_with(swift_name_suffix) { + let model = + description.split(swift_name_suffix).next().unwrap(); + return Ok(Self { + name: "iPhone".to_owned(), + model: model.to_owned(), + }); + } + // FIXME: Android + let name = description.clone(); + let model = description.clone(); + + Ok(Self { name, model }) + } + } + } +} + +impl HasSampleValues for DeviceInfoDescription { + fn sample() -> Self { + Self::new("My precious", "iPhone 15 Pro") + } + + fn sample_other() -> Self { + Self::new("R2", "OnePlus Open") + } +} + +#[cfg(test)] +mod test_device_info_description { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = DeviceInfoDescription; + + #[test] + fn json_new_format() { + let sut = SUT::sample(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "name": "My precious", + "model": "iPhone 15 Pro" + } + "#, + ) + } + + #[test] + fn json_old_format_iphone_iphone() { + let json = json!("iPhone (iPhone)"); + let sut = serde_json::from_value::(json).unwrap(); + assert_eq!(sut.clone(), SUT::new("iPhone", "iPhone")); + + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "name": "iPhone", + "model": "iPhone" + } + "#, + ); + } + + #[test] + fn json_old_format_iphone15_pro_max_iphone() { + let json = json!("iPhone 15 Pro Max (iPhone)"); + let sut = serde_json::from_value::(json).unwrap(); + assert_eq!(sut.clone(), SUT::new("iPhone", "iPhone 15 Pro Max")); + + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "name": "iPhone", + "model": "iPhone 15 Pro Max" + } + "#, + ); + } + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/src/profile/v100/header/device_info_uniffi_fn.rs b/crates/profile/src/v100/header/device_info_uniffi_fn.rs similarity index 66% rename from src/profile/v100/header/device_info_uniffi_fn.rs rename to crates/profile/src/v100/header/device_info_uniffi_fn.rs index 62eb994bf..2307b9f9c 100644 --- a/src/profile/v100/header/device_info_uniffi_fn.rs +++ b/crates/profile/src/v100/header/device_info_uniffi_fn.rs @@ -12,13 +12,6 @@ pub fn new_device_info_sample_other() -> DeviceInfo { DeviceInfo::sample_other() } -/// Instantiates a new `DeviceInfo` with "iPhone" as description, and -/// generates a new `id` and will use the current `date` for creation date. -#[uniffi::export] -pub fn new_device_info_iphone() -> DeviceInfo { - DeviceInfo::new_iphone() -} - #[cfg(test)] mod tests { use super::*; @@ -40,9 +33,4 @@ mod tests { 2 ); } - - #[test] - fn test_new_device_info_iphone() { - assert_eq!(new_device_info_iphone().description, "iPhone".to_owned()) - } } diff --git a/src/profile/v100/header/header.rs b/crates/profile/src/v100/header/header.rs similarity index 68% rename from src/profile/v100/header/header.rs rename to crates/profile/src/v100/header/header.rs index 934dfb872..0d02a35c5 100644 --- a/src/profile/v100/header/header.rs +++ b/crates/profile/src/v100/header/header.rs @@ -61,7 +61,7 @@ impl Header { /// "Unknown device" as description, and empty content hint pub fn new(creating_device: DeviceInfo) -> Self { Self::with_values( - profile_id(), + ProfileID(id()), creating_device, ContentHint::new(), now(), @@ -77,44 +77,28 @@ impl Identifiable for Header { } } -impl Default for Header { - fn default() -> Self { - Self::new(DeviceInfo::default()) - } -} - impl HasSampleValues for Header { /// A sample used to facilitate unit tests. fn sample() -> Self { - let date = Timestamp::parse("2023-09-11T16:05:56Z").unwrap(); - let device = DeviceInfo::new( - Uuid::from_str("66f07ca2-a9d9-49e5-8152-77aca3d1dd74").unwrap(), - date, - "iPhone", - ); + let device_info = DeviceInfo::sample(); Header::with_values( ProfileID::from_str("12345678-bbbb-cccc-dddd-abcd12345678") .unwrap(), - device, + device_info.clone(), ContentHint::with_counters(4, 0, 2), - date, + device_info.date, ) } /// A sample used to facilitate unit tests. fn sample_other() -> Self { - let date = Timestamp::parse("2023-12-20T16:05:56Z").unwrap(); - let device = DeviceInfo::new( - Uuid::from_str("aabbccdd-a9d9-49e5-8152-beefbeefbeef").unwrap(), - date, - "iPhone", - ); + let device_info = DeviceInfo::sample_other(); Header::with_values( ProfileID::from_str("87654321-bbbb-cccc-dddd-87654321dcba") .unwrap(), - device, + device_info.clone(), ContentHint::new(), - date, + device_info.date, ) } } @@ -149,12 +133,12 @@ pub mod tests { "creatingDevice": { "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", "date": "2023-09-11T16:05:56.000Z", - "description": "iPhone" + "description": { "name": "iPhone", "model": "iPhone" } }, "lastUsedOnDevice": { "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", "date": "2023-09-11T16:05:56.000Z", - "description": "iPhone" + "description": { "name": "iPhone", "model": "iPhone" } }, "lastModified": "2023-09-11T16:05:56.000Z", "contentHint": { @@ -167,65 +151,22 @@ pub mod tests { ); } - #[test] - fn last_updated() { - let a = SUT::default(); - let b = SUT::default(); - assert_ne!(a.last_modified, b.last_modified); - } - #[test] fn display() { - let date = Timestamp::parse("2023-09-11T16:05:56Z").unwrap(); - let device = DeviceInfo::new( - Uuid::from_str("66f07ca2-a9d9-49e5-8152-77aca3d1dd74").unwrap(), - date, - "iPhone", - ); - let sut = SUT::with_values( - ProfileID::from_str("12345678-bbbb-cccc-dddd-abcd12345678") - .unwrap(), - device, - ContentHint::new(), - date, - ); - assert_eq!(format!("{sut}"), "#12345678-bbbb-cccc-dddd-abcd12345678 v=100, content: #networks: 0, #accounts: 0, #personas: 0"); - } - - #[test] - fn creating_device() { - let value = DeviceInfo::new_iphone(); - let sut = SUT { - creating_device: value.clone(), - ..Default::default() - }; - assert_eq!(sut.creating_device, value) - } - - #[test] - fn get_id() { - let value = profile_id(); - let sut = SUT { - id: value, - ..Default::default() - }; - assert_eq!(sut.id, value) + let sut = SUT::sample(); + pretty_assertions::assert_eq!(format!("{sut}"), "#12345678-bbbb-cccc-dddd-abcd12345678 v=100, content: #networks: 2, #accounts: 4, #personas: 0"); } #[test] fn snapshot_version() { let value = ProfileSnapshotVersion::default(); - let sut = SUT { - snapshot_version: value, - ..Default::default() - }; + let sut = SUT::sample(); assert_eq!(sut.snapshot_version, value) } } #[cfg(test)] mod uniffi_tests { - use crate::{new_header_sample, new_header_sample_other, HasSampleValues}; use super::*; diff --git a/src/profile/v100/header/header_uniffi_fn.rs b/crates/profile/src/v100/header/header_uniffi_fn.rs similarity index 100% rename from src/profile/v100/header/header_uniffi_fn.rs rename to crates/profile/src/v100/header/header_uniffi_fn.rs diff --git a/src/profile/v100/header/mod.rs b/crates/profile/src/v100/header/mod.rs similarity index 72% rename from src/profile/v100/header/mod.rs rename to crates/profile/src/v100/header/mod.rs index 07d068e32..0bb4ec4ef 100644 --- a/src/profile/v100/header/mod.rs +++ b/crates/profile/src/v100/header/mod.rs @@ -1,12 +1,16 @@ mod content_hint; +mod device_id; mod device_info; +mod device_info_description; mod device_info_uniffi_fn; mod header; mod header_uniffi_fn; mod profile_id; pub use content_hint::*; +pub use device_id::*; pub use device_info::*; +pub use device_info_description::*; pub use device_info_uniffi_fn::*; pub use header::*; pub use header_uniffi_fn::*; diff --git a/src/profile/v100/header/profile_id.rs b/crates/profile/src/v100/header/profile_id.rs similarity index 91% rename from src/profile/v100/header/profile_id.rs rename to crates/profile/src/v100/header/profile_id.rs index 8118332f3..f7980624f 100644 --- a/src/profile/v100/header/profile_id.rs +++ b/crates/profile/src/v100/header/profile_id.rs @@ -13,7 +13,8 @@ use crate::prelude::*; Hash, )] #[serde(transparent)] -pub struct ProfileID(pub(crate) Uuid); +pub struct ProfileID(pub Uuid); + uniffi::custom_newtype!(ProfileID, Uuid); impl FromStr for ProfileID { @@ -29,11 +30,11 @@ impl FromStr for ProfileID { impl HasSampleValues for ProfileID { fn sample() -> Self { - ProfileID(Uuid::from_bytes([0xff; 16])) + ProfileID(Uuid::sample()) } fn sample_other() -> Self { - ProfileID(Uuid::from_bytes([0xde; 16])) + ProfileID(Uuid::sample_other()) } } diff --git a/src/profile/v100/mod.rs b/crates/profile/src/v100/mod.rs similarity index 85% rename from src/profile/v100/mod.rs rename to crates/profile/src/v100/mod.rs index 66b7a517a..81f5b5eae 100644 --- a/src/profile/v100/mod.rs +++ b/crates/profile/src/v100/mod.rs @@ -1,10 +1,8 @@ -mod address; mod app_preferences; mod entity; mod entity_security_state; mod factors; mod header; -mod json_data_convertible; mod networks; mod profile; mod profile_file_contents; @@ -12,13 +10,11 @@ mod profile_file_contents_uniffi_fn; mod profile_uniffi_fn; mod proto_profile_maybe_with_legacy_p2p_links; -pub use address::*; pub use app_preferences::*; pub use entity::*; pub use entity_security_state::*; pub use factors::*; pub use header::*; -pub use json_data_convertible::*; pub use networks::*; pub use profile::*; pub use profile_file_contents::*; diff --git a/src/profile/v100/networks/mod.rs b/crates/profile/src/v100/networks/mod.rs similarity index 100% rename from src/profile/v100/networks/mod.rs rename to crates/profile/src/v100/networks/mod.rs diff --git a/src/profile/v100/networks/network/accounts.rs b/crates/profile/src/v100/networks/network/accounts.rs similarity index 85% rename from src/profile/v100/networks/network/accounts.rs rename to crates/profile/src/v100/networks/network/accounts.rs index 94f21337b..9712d33ad 100644 --- a/src/profile/v100/networks/network/accounts.rs +++ b/crates/profile/src/v100/networks/network/accounts.rs @@ -7,6 +7,14 @@ decl_identified_vec_of!( Account ); +impl OnSameNetworkValidating for Accounts { + type Element = Account; + + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + impl HasSampleValues for Accounts { /// A sample used to facilitate unit tests. fn sample() -> Self { @@ -69,6 +77,69 @@ mod tests { ) } + #[test] + fn test_assert_elements_on_same_network_empty_is_ok_none() { + assert_eq!(SUT::new().assert_elements_on_same_network(), Ok(None)) + } + + #[test] + fn test_assert_elements_not_empty_and_on_same_network_err_if_empty() { + assert_eq!( + SUT::new().assert_elements_not_empty_and_on_same_network(), + Err(CommonError::ExpectedNonEmptyCollection) + ) + } + + #[test] + fn on_same_network_mainnet() { + assert_eq!( + SUT::sample() + .assert_elements_not_empty_and_on_same_network() + .unwrap(), + NetworkID::Mainnet + ) + } + + #[test] + fn on_same_network_throws_error_if_on_different_mainnet_first() { + assert_eq!( + SUT::from_iter([ + Account::sample_mainnet(), + Account::sample_stokenet() + ]) + .assert_elements_not_empty_and_on_same_network(), + Err(CommonError::NetworkDiscrepancy { + expected: NetworkID::Mainnet.discriminant(), + actual: NetworkID::Stokenet.discriminant() + }) + ) + } + + #[test] + fn on_same_network_throws_error_if_on_different_stokenet_first() { + assert_eq!( + SUT::from_iter([ + Account::sample_stokenet(), + Account::sample_mainnet() + ]) + .assert_elements_not_empty_and_on_same_network(), + Err(CommonError::NetworkDiscrepancy { + expected: NetworkID::Stokenet.discriminant(), + actual: NetworkID::Mainnet.discriminant() + }) + ) + } + + #[test] + fn on_same_network_stokenet() { + assert_eq!( + SUT::sample_other() + .assert_elements_not_empty_and_on_same_network() + .unwrap(), + NetworkID::Stokenet + ) + } + #[test] fn with_one() { assert_eq!(SUT::just(Account::sample()).len(), 1) @@ -88,7 +159,7 @@ mod tests { AppearanceID::default(), ); let accounts = SUT::just(account.clone()); - assert_eq!(accounts.get_id(&address), Some(&account)); + assert_eq!(accounts.get_id(address), Some(&account)); } #[test] diff --git a/src/profile/v100/networks/network/authorized_dapp/authorized_dapp.rs b/crates/profile/src/v100/networks/network/authorized_dapp/authorized_dapp.rs similarity index 99% rename from src/profile/v100/networks/network/authorized_dapp/authorized_dapp.rs rename to crates/profile/src/v100/networks/network/authorized_dapp/authorized_dapp.rs index 61d0c69a4..c1a4f5d20 100644 --- a/src/profile/v100/networks/network/authorized_dapp/authorized_dapp.rs +++ b/crates/profile/src/v100/networks/network/authorized_dapp/authorized_dapp.rs @@ -86,14 +86,6 @@ impl Identifiable for AuthorizedDapp { pub type DappDefinitionAddress = AccountAddress; -impl Identifiable for AccountAddress { - type ID = Self; - - fn id(&self) -> Self::ID { - *self - } -} - impl AuthorizedDapp { pub fn sample_mainnet_dashboard() -> Self { Self::new( diff --git a/src/profile/v100/networks/network/authorized_dapp/authorized_dapp_uniffi_fn.rs b/crates/profile/src/v100/networks/network/authorized_dapp/authorized_dapp_uniffi_fn.rs similarity index 100% rename from src/profile/v100/networks/network/authorized_dapp/authorized_dapp_uniffi_fn.rs rename to crates/profile/src/v100/networks/network/authorized_dapp/authorized_dapp_uniffi_fn.rs diff --git a/src/profile/v100/networks/network/authorized_dapp/authorized_persona_simple.rs b/crates/profile/src/v100/networks/network/authorized_dapp/authorized_persona_simple.rs similarity index 99% rename from src/profile/v100/networks/network/authorized_dapp/authorized_persona_simple.rs rename to crates/profile/src/v100/networks/network/authorized_dapp/authorized_persona_simple.rs index beb9cf6d8..ca388c435 100644 --- a/src/profile/v100/networks/network/authorized_dapp/authorized_persona_simple.rs +++ b/crates/profile/src/v100/networks/network/authorized_dapp/authorized_persona_simple.rs @@ -86,7 +86,7 @@ impl IsNetworkAware for AuthorizedPersonaSimple { } impl PersonaData { - pub(crate) fn shared_everything(&self) -> SharedPersonaData { + pub fn shared_everything(&self) -> SharedPersonaData { SharedPersonaData::new( self.name.clone().map(|x| x.id), SharedToDappWithPersonaIDsOfPersonaDataEntries::exactly( diff --git a/src/profile/v100/networks/network/authorized_dapp/authorized_persona_simple_uniffi_fn.rs b/crates/profile/src/v100/networks/network/authorized_dapp/authorized_persona_simple_uniffi_fn.rs similarity index 100% rename from src/profile/v100/networks/network/authorized_dapp/authorized_persona_simple_uniffi_fn.rs rename to crates/profile/src/v100/networks/network/authorized_dapp/authorized_persona_simple_uniffi_fn.rs diff --git a/src/profile/v100/networks/network/authorized_dapp/mod.rs b/crates/profile/src/v100/networks/network/authorized_dapp/mod.rs similarity index 95% rename from src/profile/v100/networks/network/authorized_dapp/mod.rs rename to crates/profile/src/v100/networks/network/authorized_dapp/mod.rs index e893d6072..89a4f91cd 100644 --- a/src/profile/v100/networks/network/authorized_dapp/mod.rs +++ b/crates/profile/src/v100/networks/network/authorized_dapp/mod.rs @@ -18,4 +18,4 @@ pub use shared_persona_data::*; pub use shared_persona_data_uniffi_fn::*; pub use shared_to_dapp_with_persona_account_addresses::*; pub use shared_to_dapp_with_persona_ids_of_persona_data_entries::*; -pub use shared_with_dapp::*; +pub(crate) use shared_with_dapp::*; diff --git a/src/profile/v100/networks/network/authorized_dapp/references_to_authorized_personas.rs b/crates/profile/src/v100/networks/network/authorized_dapp/references_to_authorized_personas.rs similarity index 100% rename from src/profile/v100/networks/network/authorized_dapp/references_to_authorized_personas.rs rename to crates/profile/src/v100/networks/network/authorized_dapp/references_to_authorized_personas.rs diff --git a/src/profile/v100/networks/network/authorized_dapp/shared_persona_data.rs b/crates/profile/src/v100/networks/network/authorized_dapp/shared_persona_data.rs similarity index 100% rename from src/profile/v100/networks/network/authorized_dapp/shared_persona_data.rs rename to crates/profile/src/v100/networks/network/authorized_dapp/shared_persona_data.rs diff --git a/src/profile/v100/networks/network/authorized_dapp/shared_persona_data_uniffi_fn.rs b/crates/profile/src/v100/networks/network/authorized_dapp/shared_persona_data_uniffi_fn.rs similarity index 100% rename from src/profile/v100/networks/network/authorized_dapp/shared_persona_data_uniffi_fn.rs rename to crates/profile/src/v100/networks/network/authorized_dapp/shared_persona_data_uniffi_fn.rs diff --git a/src/profile/v100/networks/network/authorized_dapp/shared_to_dapp_with_persona_account_addresses.rs b/crates/profile/src/v100/networks/network/authorized_dapp/shared_to_dapp_with_persona_account_addresses.rs similarity index 100% rename from src/profile/v100/networks/network/authorized_dapp/shared_to_dapp_with_persona_account_addresses.rs rename to crates/profile/src/v100/networks/network/authorized_dapp/shared_to_dapp_with_persona_account_addresses.rs diff --git a/src/profile/v100/networks/network/authorized_dapp/shared_to_dapp_with_persona_ids_of_persona_data_entries.rs b/crates/profile/src/v100/networks/network/authorized_dapp/shared_to_dapp_with_persona_ids_of_persona_data_entries.rs similarity index 100% rename from src/profile/v100/networks/network/authorized_dapp/shared_to_dapp_with_persona_ids_of_persona_data_entries.rs rename to crates/profile/src/v100/networks/network/authorized_dapp/shared_to_dapp_with_persona_ids_of_persona_data_entries.rs diff --git a/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs b/crates/profile/src/v100/networks/network/authorized_dapp/shared_with_dapp.rs similarity index 100% rename from src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs rename to crates/profile/src/v100/networks/network/authorized_dapp/shared_with_dapp.rs diff --git a/src/profile/v100/networks/network/authorized_dapps.rs b/crates/profile/src/v100/networks/network/authorized_dapps.rs similarity index 99% rename from src/profile/v100/networks/network/authorized_dapps.rs rename to crates/profile/src/v100/networks/network/authorized_dapps.rs index a5919980e..f2028fdb2 100644 --- a/src/profile/v100/networks/network/authorized_dapps.rs +++ b/crates/profile/src/v100/networks/network/authorized_dapps.rs @@ -85,7 +85,7 @@ mod tests { let authorized_dapp = AuthorizedDapp::sample(); let address = authorized_dapp.dapp_definition_address; let authorized_dapps = AuthorizedDapps::just(authorized_dapp.clone()); - assert_eq!(authorized_dapps.get_id(&address), Some(&authorized_dapp)); + assert_eq!(authorized_dapps.get_id(address), Some(&authorized_dapp)); } #[test] diff --git a/src/profile/v100/networks/network/mod.rs b/crates/profile/src/v100/networks/network/mod.rs similarity index 74% rename from src/profile/v100/networks/network/mod.rs rename to crates/profile/src/v100/networks/network/mod.rs index 3d6514a32..7c7435272 100644 --- a/src/profile/v100/networks/network/mod.rs +++ b/crates/profile/src/v100/networks/network/mod.rs @@ -1,8 +1,6 @@ mod accounts; mod authorized_dapp; mod authorized_dapps; -mod network_id; -mod network_id_uniffi_fn; mod personas; mod profile_network; mod profile_network_uniffi_fn; @@ -10,8 +8,6 @@ mod profile_network_uniffi_fn; pub use accounts::*; pub use authorized_dapp::*; pub use authorized_dapps::*; -pub use network_id::*; -pub use network_id_uniffi_fn::*; pub use personas::*; pub use profile_network::*; pub use profile_network_uniffi_fn::*; diff --git a/src/profile/v100/networks/network/personas.rs b/crates/profile/src/v100/networks/network/personas.rs similarity index 99% rename from src/profile/v100/networks/network/personas.rs rename to crates/profile/src/v100/networks/network/personas.rs index 0fec49d41..12627b3ea 100644 --- a/src/profile/v100/networks/network/personas.rs +++ b/crates/profile/src/v100/networks/network/personas.rs @@ -81,7 +81,7 @@ mod tests { let persona = Persona::sample(); let address = persona.address; let personas = Personas::just(persona.clone()); - assert_eq!(personas.get_id(&address), Some(&persona)); + assert_eq!(personas.get_id(address), Some(&persona)); } #[test] diff --git a/src/profile/v100/networks/network/profile_network.rs b/crates/profile/src/v100/networks/network/profile_network.rs similarity index 97% rename from src/profile/v100/networks/network/profile_network.rs rename to crates/profile/src/v100/networks/network/profile_network.rs index b553db527..0546845f0 100644 --- a/src/profile/v100/networks/network/profile_network.rs +++ b/crates/profile/src/v100/networks/network/profile_network.rs @@ -65,10 +65,12 @@ impl Identifiable for ProfileNetwork { } impl ProfileNetwork { - /// Instantiates a new `Network` from `network_id` and `accounts`. + /// Instantiates a new `ProfileNetwork` from `network_id`, `accounts`, `personas` + /// and `authorized_dapps`. /// - /// Panics if not any account in `accounts` is on another - /// network than `network_id` + /// # Panic + /// Panics if not all account in `accounts` are on network with id `network_id`, + /// and same for `personas` and `authorized_dapps`. pub fn new( network_id: impl Into, accounts: impl Into, @@ -107,6 +109,17 @@ impl ProfileNetwork { authorized_dapps, } } + + /// Instantiates a new empty `ProfileNetwork` from `network_id`, i.e. + /// Accounts, Personas, AuthorizedDapps all being empty. + pub fn new_empty_on(network_id: impl Into) -> Self { + Self::new( + network_id, + Accounts::new(), + Personas::new(), + AuthorizedDapps::new(), + ) + } } impl ProfileNetwork { diff --git a/src/profile/v100/networks/network/profile_network_uniffi_fn.rs b/crates/profile/src/v100/networks/network/profile_network_uniffi_fn.rs similarity index 100% rename from src/profile/v100/networks/network/profile_network_uniffi_fn.rs rename to crates/profile/src/v100/networks/network/profile_network_uniffi_fn.rs diff --git a/src/profile/v100/networks/profile_networks.rs b/crates/profile/src/v100/networks/profile_networks.rs similarity index 94% rename from src/profile/v100/networks/profile_networks.rs rename to crates/profile/src/v100/networks/profile_networks.rs index 9136cee25..b7c577d70 100644 --- a/src/profile/v100/networks/profile_networks.rs +++ b/crates/profile/src/v100/networks/profile_networks.rs @@ -9,7 +9,7 @@ decl_identified_vec_of!( impl ProfileNetworks { pub fn get_account(&self, address: &AccountAddress) -> Option { - self.get_id(&address.network_id()) + self.get_id(address.network_id()) .and_then(|n| n.accounts.get_id(address)) .cloned() } @@ -57,24 +57,39 @@ impl HasSampleValues for ProfileNetworks { mod tests { use super::*; + #[allow(clippy::upper_case_acronyms)] + type SUT = ProfileNetworks; + #[test] fn inequality() { - assert_ne!(ProfileNetworks::sample(), ProfileNetworks::sample_other()); + assert_ne!(SUT::sample(), SUT::sample_other()); } #[test] fn equality() { - assert_eq!(ProfileNetworks::sample(), ProfileNetworks::sample()); - assert_eq!( - ProfileNetworks::sample_other(), - ProfileNetworks::sample_other() - ); + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn append_is_noop_if_already_contains() { + let mut sut = SUT::sample(); + assert_eq!(sut.len(), 2); + assert_eq!(sut[1].accounts.len(), 2); + + let outcome = + sut.append(ProfileNetwork::new_empty_on(NetworkID::Stokenet)); + assert_eq!(outcome, (false, 1)); + + // assert NOOP + assert_eq!(sut.len(), 2); + assert_eq!(sut[1].accounts.len(), 2); } #[test] fn duplicates_are_prevented() { assert_eq!( - ProfileNetworks::from_iter( + SUT::from_iter( [ProfileNetwork::sample(), ProfileNetwork::sample()] .into_iter() ) @@ -85,7 +100,7 @@ mod tests { #[test] fn duplicates_are_prevented_and_first_added_is_retained() { - let mut sut = ProfileNetworks::from_iter([ProfileNetwork::new( + let mut sut = SUT::from_iter([ProfileNetwork::new( NetworkID::Mainnet, Accounts::from_iter([ Account::sample_mainnet_alice(), @@ -105,7 +120,7 @@ mod tests { ); assert_eq!( - sut.get_id(&NetworkID::Mainnet).unwrap().accounts.items(), + sut.get_id(NetworkID::Mainnet).unwrap().accounts.items(), [ Account::sample_mainnet_alice(), Account::sample_mainnet_bob() @@ -115,14 +130,14 @@ mod tests { #[test] fn update_account() { - let mut sut = ProfileNetworks::sample(); + let mut sut = SUT::sample(); let id = &NetworkID::Mainnet; let account_address = Account::sample().address; assert_eq!( sut.get_id(id) .unwrap() .accounts - .get_id(&account_address) + .get_id(account_address) .unwrap() .display_name .value, @@ -137,7 +152,7 @@ mod tests { sut.get_id(id) .unwrap() .accounts - .get_id(&account_address) + .get_id(account_address) .unwrap() .display_name .value, @@ -147,11 +162,11 @@ mod tests { #[test] fn update_account_unknown_network() { - let mut sut = ProfileNetworks::sample(); + let mut sut = SUT::sample(); let id = &NetworkID::Mainnet; let account_address = Account::sample_nebunet().address; assert_eq!( - sut.get_id(id).unwrap().accounts.get_id(&account_address), + sut.get_id(id).unwrap().accounts.get_id(account_address), None ); @@ -162,16 +177,16 @@ mod tests { .is_none()); // Assert unchanged - assert_eq!(sut, ProfileNetworks::sample()); + assert_eq!(sut, SUT::sample()); } #[test] fn update_account_unknown_account() { - let mut sut = ProfileNetworks::sample(); + let mut sut = SUT::sample(); let id = &NetworkID::Mainnet; let account_address = Account::sample_mainnet_carol().address; assert_eq!( - sut.get_id(id).unwrap().accounts.get_id(&account_address), + sut.get_id(id).unwrap().accounts.get_id(account_address), None ); @@ -182,7 +197,7 @@ mod tests { .is_none()); // Assert unchanged - assert_eq!(sut, ProfileNetworks::sample()); + assert_eq!(sut, SUT::sample()); } #[test] @@ -193,20 +208,20 @@ mod tests { Personas::default(), AuthorizedDapps::default(), ); - assert_eq!(ProfileNetworks::just(network).len(), 1); + assert_eq!(SUT::just(network).len(), 1); } #[test] fn content_hint() { assert_eq!( - ProfileNetworks::sample().content_hint(), + SUT::sample().content_hint(), ContentHint::with_counters(4, 0, 2) ); } #[test] fn json_roundtrip() { - let sut = ProfileNetworks::sample(); + let sut = SUT::sample(); assert_eq_after_json_roundtrip( &sut, r#" diff --git a/src/profile/v100/profile.rs b/crates/profile/src/v100/profile.rs similarity index 92% rename from src/profile/v100/profile.rs rename to crates/profile/src/v100/profile.rs index 1db720f3b..37de23bd0 100644 --- a/src/profile/v100/profile.rs +++ b/crates/profile/src/v100/profile.rs @@ -85,26 +85,60 @@ impl Profile { } impl Profile { - /// Creates a new Profile from the `DeviceFactorSource`, without any - /// networks (thus no accounts), with creating device info as "unknown". - pub fn new( + /// Creates a new Profile from the `DeviceFactorSource` and `DeviceInfo`. + /// + /// The Profile is initialized with a Mainnet ProfileNetwork, which is + /// "empty" (no Accounts, Personas etc). + /// + /// # Panics + /// Panics if the `device_factor_source` is not a BDFS and not marked "main". + pub fn from_device_factor_source( device_factor_source: DeviceFactorSource, - creating_device_name: impl AsRef, + creating_device: DeviceInfo, ) -> Self { - let creating_device_name = creating_device_name.as_ref(); - let creating_device = DeviceInfo::with_description( - format!( - "{} - {}", - creating_device_name, device_factor_source.hint.model - ) - .as_str(), - ); + if !device_factor_source.is_main_bdfs() { + panic!("DeviceFactorSource is not main BDFS"); + } + let bdfs = device_factor_source; let header = Header::new(creating_device); Self::with( header, - FactorSources::with_bdfs(device_factor_source), + FactorSources::with_bdfs(bdfs), AppPreferences::default(), - ProfileNetworks::new(), + ProfileNetworks::just(ProfileNetwork::new_empty_on( + NetworkID::Mainnet, + )), + ) + } + + /// Creates a new Profile from the `MnemonicWithPassphrase` and `DeviceInfo`, + /// by initializing a `DeviceFactorInstance` using `DeviceInfo` as source for + /// `DeviceFactorSourceHint` which will be the BDFS of the Profile. + /// + /// The Profile is initialized with a Mainnet ProfileNetwork, which is + /// "empty" (no Accounts, Personas etc). + pub fn from_mnemonic_with_passphrase( + mnemonic_with_passphrase: MnemonicWithPassphrase, + creating_device: DeviceInfo, + ) -> Self { + let bdfs = DeviceFactorSource::babylon( + true, + &mnemonic_with_passphrase, + &creating_device, + ); + Self::from_device_factor_source(bdfs, creating_device) + } + + /// Creates a new Profile from the `Mnemonic` (no passphrase) and `DeviceInfo`, + /// by initializing a `DeviceFactorInstance` using `DeviceInfo` as source for + /// `DeviceFactorSourceHint` which will be the BDFS of the Profile. + /// + /// The Profile is initialized with a Mainnet ProfileNetwork, which is + /// "empty" (no Accounts, Personas etc). + pub fn new(mnemonic: Mnemonic, creating_device: DeviceInfo) -> Self { + Self::from_mnemonic_with_passphrase( + MnemonicWithPassphrase::new(mnemonic), + creating_device, ) } @@ -117,6 +151,7 @@ impl Profile { if factor_sources.is_empty() { panic!("FactorSources MUST NOT be empty.") } + debug!("Creating new Profile, header: {:?}", &header); Self { header, factor_sources, @@ -180,8 +215,8 @@ impl Profile { .maybe_update_with(factor_source_id, |f| { S::try_from(f.clone()) .map_err(|_| CommonError::CastFactorSourceWrongKind { - expected: S::kind(), - found: f.factor_source_kind(), + expected: S::kind().to_string(), + found: f.factor_source_kind().to_string(), }) .and_then(|element| { mutate(element).map(|modified| modified.into()) @@ -260,12 +295,23 @@ mod tests { assert_eq!(SUT::sample_other(), SUT::sample_other()); } + #[test] + fn new_creates_empty_mainnet_network() { + let sut = SUT::new(Mnemonic::sample(), DeviceInfo::sample()); + assert_eq!( + sut.networks, + ProfileNetworks::just(ProfileNetwork::new_empty_on( + NetworkID::Mainnet + )) + ); + } + #[should_panic(expected = "FactorSources MUST NOT be empty.")] #[test] fn not_allowed_to_create_profile_with_empty_factor_source() { let _ = SUT::with( Header::sample(), - IdentifiedVecOf::new(), + FactorSources::new(), AppPreferences::sample(), ProfileNetworks::sample(), ); @@ -302,6 +348,15 @@ mod tests { ); } + #[test] + #[should_panic(expected = "DeviceFactorSource is not main BDFS")] + fn new_from_non_main_bdfs_panics() { + let _ = SUT::from_device_factor_source( + DeviceFactorSource::sample_other(), + DeviceInfo::sample(), + ); + } + #[test] fn update_factor_source_not_update_when_factor_source_not_found() { let mut sut = SUT::sample(); @@ -321,9 +376,9 @@ mod tests { fn change_supported_curve_of_factor_source() { let mut sut = SUT::sample(); let id: &FactorSourceID = &DeviceFactorSource::sample().id.into(); - assert!(sut - .factor_sources - .contains_id(&DeviceFactorSource::sample().id.into())); + assert!(sut.factor_sources.contains_id(FactorSourceID::from( + DeviceFactorSource::sample().id + ))); assert_eq!( sut.factor_sources @@ -375,9 +430,9 @@ mod tests { let mut sut = SUT::sample(); let id: &FactorSourceID = &DeviceFactorSource::sample().id.into(); - assert!(sut - .factor_sources - .contains_id(&DeviceFactorSource::sample().id.into())); + assert!(sut.factor_sources.contains_id(FactorSourceID::from( + DeviceFactorSource::sample().id + ))); assert_eq!( sut.factor_sources @@ -402,8 +457,8 @@ mod tests { } ), Err(CommonError::CastFactorSourceWrongKind { - expected: FactorSourceKind::LedgerHQHardwareWallet, - found: FactorSourceKind::Device + expected: FactorSourceKind::LedgerHQHardwareWallet.to_string(), + found: FactorSourceKind::Device.to_string() }) ); @@ -449,7 +504,7 @@ mod tests { let mut sut = SUT::sample(); let account = sut .networks - .get_id(&NetworkID::Mainnet) + .get_id(NetworkID::Mainnet) .unwrap() .accounts .get_at_index(0) @@ -464,7 +519,7 @@ mod tests { assert_eq!( sut.networks - .get_id(&NetworkID::Mainnet) + .get_id(NetworkID::Mainnet) .unwrap() .accounts .get_at_index(0) @@ -479,16 +534,7 @@ mod tests { fn hash() { let n = 100; let set = (0..n) - .map(|_| { - SUT::new( - PrivateHierarchicalDeterministicFactorSource::generate_new_babylon( - true, - WalletClientModel::Unknown, - ) - .factor_source, - "Foo", - ) - }) + .map(|_| SUT::new(Mnemonic::generate_new(), DeviceInfo::sample())) .collect::>(); assert_eq!(set.len(), n); } @@ -643,12 +689,12 @@ mod tests { "creatingDevice": { "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", "date": "2023-09-11T16:05:56.000Z", - "description": "iPhone" + "description": { "name": "iPhone", "model": "iPhone" } }, "lastUsedOnDevice": { "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", "date": "2023-09-11T16:05:56.000Z", - "description": "iPhone" + "description": { "name": "iPhone", "model": "iPhone" } }, "lastModified": "2023-09-11T16:05:56.000Z", "contentHint": { @@ -722,16 +768,8 @@ mod tests { "fiatCurrencyPriceTarget": "usd" }, "gateways": { - "current": "https://rcnet-v3.radixdlt.com/", + "current": "https://mainnet.radixdlt.com/", "saved": [ - { - "network": { - "name": "zabanet", - "id": 14, - "displayDescription": "RCnet-V3 (Test Network)" - }, - "url": "https://rcnet-v3.radixdlt.com/" - }, { "network": { "name": "mainnet", diff --git a/src/profile/v100/profile_file_contents.rs b/crates/profile/src/v100/profile_file_contents.rs similarity index 100% rename from src/profile/v100/profile_file_contents.rs rename to crates/profile/src/v100/profile_file_contents.rs diff --git a/src/profile/v100/profile_file_contents_uniffi_fn.rs b/crates/profile/src/v100/profile_file_contents_uniffi_fn.rs similarity index 77% rename from src/profile/v100/profile_file_contents_uniffi_fn.rs rename to crates/profile/src/v100/profile_file_contents_uniffi_fn.rs index 03693d69b..4f72baba2 100644 --- a/src/profile/v100/profile_file_contents_uniffi_fn.rs +++ b/crates/profile/src/v100/profile_file_contents_uniffi_fn.rs @@ -1,13 +1,12 @@ use crate::prelude::*; -use std::hash::{DefaultHasher, Hash, Hasher}; #[uniffi::export] -pub(crate) fn new_profile_file_contents_sample() -> ProfileFileContents { +pub fn new_profile_file_contents_sample() -> ProfileFileContents { ProfileFileContents::sample() } #[uniffi::export] -pub(crate) fn new_profile_file_contents_sample_other() -> ProfileFileContents { +pub fn new_profile_file_contents_sample_other() -> ProfileFileContents { ProfileFileContents::sample_other() } diff --git a/src/profile/v100/profile_uniffi_fn.rs b/crates/profile/src/v100/profile_uniffi_fn.rs similarity index 86% rename from src/profile/v100/profile_uniffi_fn.rs rename to crates/profile/src/v100/profile_uniffi_fn.rs index 2c8be3444..a90528f11 100644 --- a/src/profile/v100/profile_uniffi_fn.rs +++ b/crates/profile/src/v100/profile_uniffi_fn.rs @@ -2,12 +2,22 @@ use crate::prelude::*; json_data_convertible!(Profile); +#[uniffi::export] +pub fn new_profile_with_mnemonic( + mnemonic: Mnemonic, + device_info: DeviceInfo, +) -> Profile { + Profile::new(mnemonic, device_info) +} + +/// # Panics +/// Panics if `device_factor_source` is not a main BDFS. #[uniffi::export] pub fn new_profile( device_factor_source: DeviceFactorSource, - creating_device_name: String, + device_info: DeviceInfo, ) -> Profile { - Profile::new(device_factor_source, creating_device_name.as_str()) + Profile::from_device_factor_source(device_factor_source, device_info) } #[uniffi::export] @@ -100,13 +110,23 @@ mod uniffi_tests { ) } + #[test] + fn test_new_with_mnemonic() { + assert_eq!( + new_profile_with_mnemonic(Mnemonic::sample(), DeviceInfo::sample()) + .bdfs() + .id, + Profile::new(Mnemonic::sample(), DeviceInfo::sample()) + .bdfs() + .id, + ); + } + #[test] fn new_private_hd() { let private = PrivateHierarchicalDeterministicFactorSource::sample(); - let lhs = super::new_profile( - private.factor_source.clone(), - "iPhone".to_string(), - ); + let lhs = + new_profile(private.factor_source.clone(), DeviceInfo::sample()); assert_eq!( lhs.bdfs().factor_source_id(), private.factor_source.factor_source_id() @@ -115,8 +135,8 @@ mod uniffi_tests { #[test] fn to_string_and_debug_string() { - assert_eq!(profile_to_string(&SUT::sample()).len(), 4314); - assert_eq!(profile_to_debug_string(&SUT::sample()).len(), 27076); + assert_eq!(profile_to_string(&SUT::sample()).len(), 4282); + assert_eq!(profile_to_debug_string(&SUT::sample()).len(), 27236); assert_ne!( profile_to_debug_string(&SUT::sample()), profile_to_debug_string(&SUT::sample_other()) diff --git a/src/profile/v100/proto_profile_maybe_with_legacy_p2p_links.rs b/crates/profile/src/v100/proto_profile_maybe_with_legacy_p2p_links.rs similarity index 100% rename from src/profile/v100/proto_profile_maybe_with_legacy_p2p_links.rs rename to crates/profile/src/v100/proto_profile_maybe_with_legacy_p2p_links.rs diff --git a/crates/profile/uniffi.toml b/crates/profile/uniffi.toml new file mode 100644 index 000000000..ada3fb074 --- /dev/null +++ b/crates/profile/uniffi.toml @@ -0,0 +1,8 @@ +namespace = "profile" + +[bindings.swift] +module_name = "SargonProfile" +experimental_sendable_value_types = true + +[bindings.kotlin] +package_name = "com.radixdlt.sargon.profile" diff --git a/crates/radix_connect/Cargo.toml b/crates/radix_connect/Cargo.toml new file mode 100644 index 000000000..94ed9c85b --- /dev/null +++ b/crates/radix_connect/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "radix_connect" +version = "1.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } +paste = { workspace = true } +url = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +pretty_assertions = { workspace = true } +actix-rt = { workspace = true } +serde_with = { workspace = true } +derive_more = { workspace = true } +sargoncommon = { path = "../common" } +profile = { path = "../profile" } +clients = { path = "../clients" } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } + +[lib] +crate-type = ["staticlib", "cdylib", "lib"] diff --git a/crates/radix_connect/build.rs b/crates/radix_connect/build.rs new file mode 100644 index 000000000..84ca4b708 --- /dev/null +++ b/crates/radix_connect/build.rs @@ -0,0 +1,4 @@ +pub fn main() { + uniffi::generate_scaffolding("src/radix_connect.udl") + .expect("Should be able to build."); +} diff --git a/src/radix_connect/interaction_id.rs b/crates/radix_connect/src/interaction_id.rs similarity index 86% rename from src/radix_connect/interaction_id.rs rename to crates/radix_connect/src/interaction_id.rs index 2801d8205..da9358a88 100644 --- a/src/radix_connect/interaction_id.rs +++ b/crates/radix_connect/src/interaction_id.rs @@ -1,11 +1,15 @@ use crate::prelude::*; -uniffi::custom_newtype!(WalletInteractionId, Uuid); +uniffi::custom_type!(WalletInteractionId, Uuid, { + remote, + from_custom: |id| id.0, + try_into_custom: |val| Ok(WalletInteractionId(val)) +}); #[derive( Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, )] -pub struct WalletInteractionId(pub(crate) Uuid); +pub struct WalletInteractionId(pub Uuid); impl FromStr for WalletInteractionId { type Err = CommonError; diff --git a/src/radix_connect/interaction_version.rs b/crates/radix_connect/src/interaction_version.rs similarity index 100% rename from src/radix_connect/interaction_version.rs rename to crates/radix_connect/src/interaction_version.rs diff --git a/crates/radix_connect/src/lib.rs b/crates/radix_connect/src/lib.rs new file mode 100644 index 000000000..09cc3bf98 --- /dev/null +++ b/crates/radix_connect/src/lib.rs @@ -0,0 +1,27 @@ +mod interaction_id; +mod interaction_version; +mod p2p_links; +mod wallet_account; +mod wallet_interaction; +mod wallet_persona; + +#[allow(dead_code)] +mod mobile; + +uniffi::remote_type!(Url, sargoncommon); +uniffi::remote_type!(Uuid, sargoncommon); + +pub mod prelude { + pub use crate::interaction_id::*; + pub use crate::interaction_version::*; + pub use crate::p2p_links::*; + pub use crate::wallet_account::*; + pub use crate::wallet_interaction::*; + pub use crate::wallet_persona::*; + + pub use clients::prelude::*; +} + +pub use prelude::*; + +uniffi::include_scaffolding!("radix_connect"); diff --git a/src/radix_connect/mobile/client.rs b/crates/radix_connect/src/mobile/client.rs similarity index 88% rename from src/radix_connect/mobile/client.rs rename to crates/radix_connect/src/mobile/client.rs index 988d7e47c..d17139f70 100644 --- a/src/radix_connect/mobile/client.rs +++ b/crates/radix_connect/src/mobile/client.rs @@ -10,12 +10,12 @@ use crate::prelude::*; // // Provisional API // #[uniffi::export] // impl RadixConnectMobile { -// // RadixConnectMobile should require a NetworkAntenna and a SecureStorage from the Wallet. +// // RadixConnectMobile should require a NetworkingDriver and a SecureStorageDriver from the Wallet. // // The internal components, such as RadixConnectRelayService will be created by the RadixConnectMobile. // #[uniffi::constructor] // pub fn new( -// _network_antenna: Arc, -// _secure_storage: Arc, +// _network_antenna: Arc, +// _secure_storage: Arc, // ) -> Self { // todo!() // } diff --git a/src/radix_connect/mobile/deep_link_parsing/connect_request.rs b/crates/radix_connect/src/mobile/deep_link_parsing/connect_request.rs similarity index 100% rename from src/radix_connect/mobile/deep_link_parsing/connect_request.rs rename to crates/radix_connect/src/mobile/deep_link_parsing/connect_request.rs diff --git a/src/radix_connect/mobile/deep_link_parsing/dapp_request.rs b/crates/radix_connect/src/mobile/deep_link_parsing/dapp_request.rs similarity index 98% rename from src/radix_connect/mobile/deep_link_parsing/dapp_request.rs rename to crates/radix_connect/src/mobile/deep_link_parsing/dapp_request.rs index f6113ef23..6643ef41b 100644 --- a/src/radix_connect/mobile/deep_link_parsing/dapp_request.rs +++ b/crates/radix_connect/src/mobile/deep_link_parsing/dapp_request.rs @@ -18,7 +18,7 @@ impl RadixConnectMobileDappRequest { } } - pub(crate) fn try_with_interaction_id_and_session_id( + pub fn try_with_interaction_id_and_session_id( interaction_id: impl AsRef, session_id: impl AsRef, ) -> Result { diff --git a/src/radix_connect/mobile/deep_link_parsing/link_request.rs b/crates/radix_connect/src/mobile/deep_link_parsing/link_request.rs similarity index 97% rename from src/radix_connect/mobile/deep_link_parsing/link_request.rs rename to crates/radix_connect/src/mobile/deep_link_parsing/link_request.rs index 3c37c1060..d547a6807 100644 --- a/src/radix_connect/mobile/deep_link_parsing/link_request.rs +++ b/crates/radix_connect/src/mobile/deep_link_parsing/link_request.rs @@ -13,7 +13,7 @@ impl RadixConnectMobileLinkRequest { Self { origin, session_id } } - pub(crate) fn try_with_origin_and_session_id( + pub fn try_with_origin_and_session_id( origin: impl AsRef, session_id: impl AsRef, ) -> Result { diff --git a/src/radix_connect/mobile/deep_link_parsing/mod.rs b/crates/radix_connect/src/mobile/deep_link_parsing/mod.rs similarity index 100% rename from src/radix_connect/mobile/deep_link_parsing/mod.rs rename to crates/radix_connect/src/mobile/deep_link_parsing/mod.rs diff --git a/src/radix_connect/mobile/deep_link_parsing/parser.rs b/crates/radix_connect/src/mobile/deep_link_parsing/parser.rs similarity index 99% rename from src/radix_connect/mobile/deep_link_parsing/parser.rs rename to crates/radix_connect/src/mobile/deep_link_parsing/parser.rs index ebb787306..f1b745898 100644 --- a/src/radix_connect/mobile/deep_link_parsing/parser.rs +++ b/crates/radix_connect/src/mobile/deep_link_parsing/parser.rs @@ -1,6 +1,4 @@ use crate::prelude::*; -use url::form_urlencoded; -use url::Url; use super::*; diff --git a/src/radix_connect/mobile/mod.rs b/crates/radix_connect/src/mobile/mod.rs similarity index 58% rename from src/radix_connect/mobile/mod.rs rename to crates/radix_connect/src/mobile/mod.rs index 7a959e8bd..1a06c139d 100644 --- a/src/radix_connect/mobile/mod.rs +++ b/crates/radix_connect/src/mobile/mod.rs @@ -3,6 +3,14 @@ mod deep_link_parsing; mod relay_service; mod session; +#[allow(unused)] pub use client::*; + +#[allow(unused)] pub use deep_link_parsing::*; + +#[allow(unused)] +pub use relay_service::*; + +#[allow(unused)] pub use session::*; diff --git a/crates/radix_connect/src/mobile/relay_service/mod.rs b/crates/radix_connect/src/mobile/relay_service/mod.rs new file mode 100644 index 000000000..7ac224d46 --- /dev/null +++ b/crates/radix_connect/src/mobile/relay_service/mod.rs @@ -0,0 +1,8 @@ +mod models; +mod service; + +#[allow(unused)] +pub use models::*; + +#[allow(unused)] +pub use service::*; diff --git a/src/gateway_api/models/types/mod.rs b/crates/radix_connect/src/mobile/relay_service/models/mod.rs similarity index 100% rename from src/gateway_api/models/types/mod.rs rename to crates/radix_connect/src/mobile/relay_service/models/mod.rs diff --git a/src/radix_connect/mobile/relay_service/models/request/mod.rs b/crates/radix_connect/src/mobile/relay_service/models/request/mod.rs similarity index 70% rename from src/radix_connect/mobile/relay_service/models/request/mod.rs rename to crates/radix_connect/src/mobile/relay_service/models/request/mod.rs index 7f4da52f0..29b1d3b7a 100644 --- a/src/radix_connect/mobile/relay_service/models/request/mod.rs +++ b/crates/radix_connect/src/mobile/relay_service/models/request/mod.rs @@ -1,5 +1,7 @@ mod request; mod request_method; +#[allow(unused)] pub use request::*; +#[allow(unused)] pub use request_method::*; diff --git a/src/radix_connect/mobile/relay_service/models/request/request.rs b/crates/radix_connect/src/mobile/relay_service/models/request/request.rs similarity index 99% rename from src/radix_connect/mobile/relay_service/models/request/request.rs rename to crates/radix_connect/src/mobile/relay_service/models/request/request.rs index 8fca256e0..716048980 100644 --- a/src/radix_connect/mobile/relay_service/models/request/request.rs +++ b/crates/radix_connect/src/mobile/relay_service/models/request/request.rs @@ -1,6 +1,6 @@ use super::request_method::Method; +use crate::mobile::session::session_id::SessionID; use crate::prelude::*; -use crate::radix_connect::mobile::session::session_id::SessionID; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] diff --git a/src/radix_connect/mobile/relay_service/models/request/request_method.rs b/crates/radix_connect/src/mobile/relay_service/models/request/request_method.rs similarity index 100% rename from src/radix_connect/mobile/relay_service/models/request/request_method.rs rename to crates/radix_connect/src/mobile/relay_service/models/request/request_method.rs diff --git a/src/radix_connect/mobile/relay_service/models/response/mod.rs b/crates/radix_connect/src/mobile/relay_service/models/response/mod.rs similarity index 63% rename from src/radix_connect/mobile/relay_service/models/response/mod.rs rename to crates/radix_connect/src/mobile/relay_service/models/response/mod.rs index 8f763e612..5937d5e90 100644 --- a/src/radix_connect/mobile/relay_service/models/response/mod.rs +++ b/crates/radix_connect/src/mobile/relay_service/models/response/mod.rs @@ -3,5 +3,3 @@ mod session_handshake_response; mod wallet_interaction_requests; pub use session_handshake_request::*; -pub use session_handshake_response::*; -pub use wallet_interaction_requests::*; diff --git a/src/radix_connect/mobile/relay_service/models/response/session_handshake_request.rs b/crates/radix_connect/src/mobile/relay_service/models/response/session_handshake_request.rs similarity index 100% rename from src/radix_connect/mobile/relay_service/models/response/session_handshake_request.rs rename to crates/radix_connect/src/mobile/relay_service/models/response/session_handshake_request.rs diff --git a/src/radix_connect/mobile/relay_service/models/response/session_handshake_response.rs b/crates/radix_connect/src/mobile/relay_service/models/response/session_handshake_response.rs similarity index 100% rename from src/radix_connect/mobile/relay_service/models/response/session_handshake_response.rs rename to crates/radix_connect/src/mobile/relay_service/models/response/session_handshake_response.rs diff --git a/src/radix_connect/mobile/relay_service/models/response/wallet_interaction_requests.rs b/crates/radix_connect/src/mobile/relay_service/models/response/wallet_interaction_requests.rs similarity index 100% rename from src/radix_connect/mobile/relay_service/models/response/wallet_interaction_requests.rs rename to crates/radix_connect/src/mobile/relay_service/models/response/wallet_interaction_requests.rs diff --git a/src/radix_connect/mobile/relay_service/service.rs b/crates/radix_connect/src/mobile/relay_service/service.rs similarity index 73% rename from src/radix_connect/mobile/relay_service/service.rs rename to crates/radix_connect/src/mobile/relay_service/service.rs index 965009278..eced7692a 100644 --- a/src/radix_connect/mobile/relay_service/service.rs +++ b/crates/radix_connect/src/mobile/relay_service/service.rs @@ -18,7 +18,7 @@ impl Service { } pub fn new_with_network_antenna( - network_antenna: Arc, + network_antenna: Arc, ) -> Self { Self::new(HttpClient::new(network_antenna)) } @@ -27,7 +27,11 @@ impl Service { const SERVICE_PATH: &str = "https://radix-connect-relay-dev.rdx-works-main.extratools.works/api/v1"; -impl NetworkRequest { +pub trait FromRadixConnectRelayRequest { + fn radix_connect_relay_request(request: Request) -> Result; +} + +impl FromRadixConnectRelayRequest for NetworkRequest { fn radix_connect_relay_request(request: Request) -> Result { NetworkRequest::new_post(Url::from_str(SERVICE_PATH).unwrap()) .with_serializing_body(request) @@ -229,66 +233,70 @@ mod tests { #[actix_rt::test] async fn test_send_wallet_interaction_response() { - let mock_antenna = MockAntenna::with_spy(200, (), |request| { - // Prepare encryption keys - let mut encryption_key = Session::sample().encryption_key; - let mut decryption_key = encryption_key; - - // Serialize the response - let wallet_to_dapp_interaction_response = - WalletToDappInteractionResponse::sample(); - let body = wallet_to_dapp_interaction_response_to_json_bytes( - &wallet_to_dapp_interaction_response, - ); - - // Encrypt the response - let encrypted = EncryptionScheme::default() - .encrypt(body.to_vec(), &mut encryption_key); - let relay_request = Request::new_send_response( - SessionID::sample(), - encrypted.clone(), - ); - let encoded = serde_json::to_vec(&relay_request).unwrap(); - - // Request that is expected to be sent - let expected_request = NetworkRequest { - url: Url::from_str(SERVICE_PATH).unwrap(), - method: NetworkMethod::Post, - body: encoded.into(), - headers: HashMap::new(), - }; - - pretty_assertions::assert_eq!(request.url, expected_request.url); - pretty_assertions::assert_eq!( - request.method, - expected_request.method - ); - - let sent_request: Request = - serde_json::from_slice(&expected_request.body).unwrap(); - pretty_assertions::assert_eq!( - sent_request.session_id, - relay_request.session_id - ); - pretty_assertions::assert_eq!( - sent_request.method, - relay_request.method - ); - - let decrypted_payload = EncryptionScheme::default() - .decrypt( - sent_request.data.unwrap().to_vec(), - &mut decryption_key, - ) - .unwrap(); - let decoded_payload: WalletToDappInteractionResponse = - serde_json::from_slice(&decrypted_payload).unwrap(); + let mock_antenna = + MockAntenna::with_spy(200, BagOfBytes::new(), |request| { + // Prepare encryption keys + let mut encryption_key = Session::sample().encryption_key; + let mut decryption_key = encryption_key; + + // Serialize the response + let wallet_to_dapp_interaction_response = + WalletToDappInteractionResponse::sample(); + let body = wallet_to_dapp_interaction_response_to_json_bytes( + &wallet_to_dapp_interaction_response, + ); + + // Encrypt the response + let encrypted = EncryptionScheme::default() + .encrypt(body.to_vec(), &mut encryption_key); + let relay_request = Request::new_send_response( + SessionID::sample(), + encrypted.clone(), + ); + let encoded = serde_json::to_vec(&relay_request).unwrap(); + + // Request that is expected to be sent + let expected_request = NetworkRequest { + url: Url::from_str(SERVICE_PATH).unwrap(), + method: NetworkMethod::Post, + body: encoded.into(), + headers: HashMap::new(), + }; + + pretty_assertions::assert_eq!( + request.url, + expected_request.url + ); + pretty_assertions::assert_eq!( + request.method, + expected_request.method + ); + + let sent_request: Request = + serde_json::from_slice(&expected_request.body).unwrap(); + pretty_assertions::assert_eq!( + sent_request.session_id, + relay_request.session_id + ); + pretty_assertions::assert_eq!( + sent_request.method, + relay_request.method + ); - pretty_assertions::assert_eq!( - decoded_payload, - wallet_to_dapp_interaction_response - ) - }); + let decrypted_payload = EncryptionScheme::default() + .decrypt( + sent_request.data.unwrap().to_vec(), + &mut decryption_key, + ) + .unwrap(); + let decoded_payload: WalletToDappInteractionResponse = + serde_json::from_slice(&decrypted_payload).unwrap(); + + pretty_assertions::assert_eq!( + decoded_payload, + wallet_to_dapp_interaction_response + ) + }); let service = Service::new_with_network_antenna(Arc::new(mock_antenna)); let session = Session::sample(); @@ -377,30 +385,37 @@ mod tests { #[actix_rt::test] async fn test_send_session_handshake_response() { - let mock_antenna = MockAntenna::with_spy(200, (), |request| { - let public_key = PublicKey::sample(); - let body = Request::new( - Method::SendHandshakeResponse, - SessionID::sample(), - BagOfBytes::from_hex(public_key.to_hex().as_str()).unwrap(), - ); - - let encoded = serde_json::to_vec(&body).unwrap(); - - let expected_request = NetworkRequest { - url: Url::from_str(SERVICE_PATH).unwrap(), - method: NetworkMethod::Post, - body: encoded.into(), - headers: HashMap::new(), - }; - - pretty_assertions::assert_eq!(request.url, expected_request.url); - pretty_assertions::assert_eq!( - request.method, - expected_request.method - ); - pretty_assertions::assert_eq!(request.body, expected_request.body); - }); + let mock_antenna = + MockAntenna::with_spy(200, BagOfBytes::new(), |request| { + let public_key = PublicKey::sample(); + let body = Request::new( + Method::SendHandshakeResponse, + SessionID::sample(), + BagOfBytes::from_hex(public_key.to_hex().as_str()).unwrap(), + ); + + let encoded = serde_json::to_vec(&body).unwrap(); + + let expected_request = NetworkRequest { + url: Url::from_str(SERVICE_PATH).unwrap(), + method: NetworkMethod::Post, + body: encoded.into(), + headers: HashMap::new(), + }; + + pretty_assertions::assert_eq!( + request.url, + expected_request.url + ); + pretty_assertions::assert_eq!( + request.method, + expected_request.method + ); + pretty_assertions::assert_eq!( + request.body, + expected_request.body + ); + }); let service = Service::new_with_network_antenna(Arc::new(mock_antenna)); let session_id = SessionID::sample(); diff --git a/src/radix_connect/mobile/session/mod.rs b/crates/radix_connect/src/mobile/session/mod.rs similarity index 78% rename from src/radix_connect/mobile/session/mod.rs rename to crates/radix_connect/src/mobile/session/mod.rs index a8a48aa44..f112685f0 100644 --- a/src/radix_connect/mobile/session/mod.rs +++ b/crates/radix_connect/src/mobile/session/mod.rs @@ -4,4 +4,3 @@ mod session_origin; pub use session::*; pub use session_id::*; -pub use session_origin::*; diff --git a/src/radix_connect/mobile/session/session.rs b/crates/radix_connect/src/mobile/session/session.rs similarity index 100% rename from src/radix_connect/mobile/session/session.rs rename to crates/radix_connect/src/mobile/session/session.rs diff --git a/src/radix_connect/mobile/session/session_id.rs b/crates/radix_connect/src/mobile/session/session_id.rs similarity index 96% rename from src/radix_connect/mobile/session/session_id.rs rename to crates/radix_connect/src/mobile/session/session_id.rs index 1bfb41372..2d083e366 100644 --- a/src/radix_connect/mobile/session/session_id.rs +++ b/crates/radix_connect/src/mobile/session/session_id.rs @@ -14,7 +14,7 @@ uniffi::custom_newtype!(SessionID, Uuid); Hash, derive_more::Display, )] -pub struct SessionID(pub(crate) Uuid); +pub struct SessionID(pub Uuid); impl FromStr for SessionID { type Err = CommonError; diff --git a/src/radix_connect/mobile/session/session_origin.rs b/crates/radix_connect/src/mobile/session/session_origin.rs similarity index 100% rename from src/radix_connect/mobile/session/session_origin.rs rename to crates/radix_connect/src/mobile/session/session_origin.rs diff --git a/src/radix_connect/p2p_links/link_connection_qr_data.rs b/crates/radix_connect/src/p2p_links/link_connection_qr_data.rs similarity index 100% rename from src/radix_connect/p2p_links/link_connection_qr_data.rs rename to crates/radix_connect/src/p2p_links/link_connection_qr_data.rs diff --git a/src/radix_connect/p2p_links/link_connection_qr_data_uniffi_fn.rs b/crates/radix_connect/src/p2p_links/link_connection_qr_data_uniffi_fn.rs similarity index 100% rename from src/radix_connect/p2p_links/link_connection_qr_data_uniffi_fn.rs rename to crates/radix_connect/src/p2p_links/link_connection_qr_data_uniffi_fn.rs diff --git a/src/radix_connect/p2p_links/mod.rs b/crates/radix_connect/src/p2p_links/mod.rs similarity index 100% rename from src/radix_connect/p2p_links/mod.rs rename to crates/radix_connect/src/p2p_links/mod.rs diff --git a/src/radix_connect/p2p_links/p2p_link.rs b/crates/radix_connect/src/p2p_links/p2p_link.rs similarity index 100% rename from src/radix_connect/p2p_links/p2p_link.rs rename to crates/radix_connect/src/p2p_links/p2p_link.rs diff --git a/src/radix_connect/p2p_links/p2p_link_uniffi_fn.rs b/crates/radix_connect/src/p2p_links/p2p_link_uniffi_fn.rs similarity index 100% rename from src/radix_connect/p2p_links/p2p_link_uniffi_fn.rs rename to crates/radix_connect/src/p2p_links/p2p_link_uniffi_fn.rs diff --git a/src/radix_connect/p2p_links/p2p_links.rs b/crates/radix_connect/src/p2p_links/p2p_links.rs similarity index 98% rename from src/radix_connect/p2p_links/p2p_links.rs rename to crates/radix_connect/src/p2p_links/p2p_links.rs index bda1b4ee8..b99a11f49 100644 --- a/src/radix_connect/p2p_links/p2p_links.rs +++ b/crates/radix_connect/src/p2p_links/p2p_links.rs @@ -1,5 +1,7 @@ use crate::prelude::*; +use profile::decl_identified_vec_of; + decl_identified_vec_of!( /// Collection of clients user have connected P2P with, typically these /// are WebRTC connections with the dApp or Connector Extension diff --git a/src/radix_connect/p2p_links/p2p_links_uniffi_fn.rs b/crates/radix_connect/src/p2p_links/p2p_links_uniffi_fn.rs similarity index 100% rename from src/radix_connect/p2p_links/p2p_links_uniffi_fn.rs rename to crates/radix_connect/src/p2p_links/p2p_links_uniffi_fn.rs diff --git a/src/radix_connect/p2p_links/radix_connect_password.rs b/crates/radix_connect/src/p2p_links/radix_connect_password.rs similarity index 100% rename from src/radix_connect/p2p_links/radix_connect_password.rs rename to crates/radix_connect/src/p2p_links/radix_connect_password.rs diff --git a/src/radix_connect/p2p_links/radix_connect_purpose.rs b/crates/radix_connect/src/p2p_links/radix_connect_purpose.rs similarity index 100% rename from src/radix_connect/p2p_links/radix_connect_purpose.rs rename to crates/radix_connect/src/p2p_links/radix_connect_purpose.rs diff --git a/crates/radix_connect/src/radix_connect.udl b/crates/radix_connect/src/radix_connect.udl new file mode 100644 index 000000000..45b51acd4 --- /dev/null +++ b/crates/radix_connect/src/radix_connect.udl @@ -0,0 +1 @@ +namespace radix_connect {}; diff --git a/src/radix_connect/wallet_account.rs b/crates/radix_connect/src/wallet_account.rs similarity index 100% rename from src/radix_connect/wallet_account.rs rename to crates/radix_connect/src/wallet_account.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata_unvalidated.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata_unvalidated.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata_unvalidated.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata_unvalidated.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/items.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/items.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/items.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/items.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/auth.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/auth.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/auth.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/auth.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/login_with_challenge.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/login_with_challenge.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/login_with_challenge.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/login_with_challenge.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/use_persona.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/use_persona.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/use_persona.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/auth/use_persona.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/authorized_request.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/authorized_request.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/authorized_request.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/authorized_request.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/accounts.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/accounts.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/accounts.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/accounts.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/persona_data.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/persona_data.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/persona_data.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/persona_data.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/reset.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/reset.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/reset.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/entity/reset.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/unauthorized_request.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/unauthorized_request.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/unauthorized_request.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/request/unauthorized_request.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/transaction.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/transaction.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/transaction.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/transaction.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/transaction_version.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/transaction_version.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/transaction_version.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_items/transaction/transaction_version.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_uniffi_fn.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_uniffi_fn.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_uniffi_fn.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_uniffi_fn.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_unvalidated.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_unvalidated.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_unvalidated.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/interaction_unvalidated.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/error_type.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/error_type.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/error_type.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/error_type.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/failure.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/failure.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/failure.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/failure.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/failure_response/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/response.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/response.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/response.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/response.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/account_proof.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/account_proof.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/account_proof.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/account_proof.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/accounts.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/accounts.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/accounts.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/accounts.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/account/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_login_with_challenge.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_login_with_challenge.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_login_with_challenge.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_login_with_challenge.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_login_without_challenge.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_login_without_challenge.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_login_without_challenge.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_login_without_challenge.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_proof.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_proof.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_proof.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_proof.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_use_persona.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_use_persona.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_use_persona.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/auth_use_persona.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/auth/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/authorized_request.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/authorized_request.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/authorized_request.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/authorized_request.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/items.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/items.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/items.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/items.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/persona_data/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/persona_data/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/persona_data/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/persona_data/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/persona_data/persona_data.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/persona_data/persona_data.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/persona_data/persona_data.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/persona_data/persona_data.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/success.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/success.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/success.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/success.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/transaction/mod.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/transaction/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/transaction/mod.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/transaction/mod.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/transaction/transaction.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/transaction/transaction.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/transaction/transaction.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/transaction/transaction.rs diff --git a/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/unauthorized_request.rs b/crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/unauthorized_request.rs similarity index 100% rename from src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/unauthorized_request.rs rename to crates/radix_connect/src/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/unauthorized_request.rs diff --git a/src/radix_connect/wallet_interaction/mod.rs b/crates/radix_connect/src/wallet_interaction/mod.rs similarity index 100% rename from src/radix_connect/wallet_interaction/mod.rs rename to crates/radix_connect/src/wallet_interaction/mod.rs diff --git a/src/radix_connect/wallet_persona.rs b/crates/radix_connect/src/wallet_persona.rs similarity index 100% rename from src/radix_connect/wallet_persona.rs rename to crates/radix_connect/src/wallet_persona.rs diff --git a/crates/radix_connect/uniffi.toml b/crates/radix_connect/uniffi.toml new file mode 100644 index 000000000..b3b523547 --- /dev/null +++ b/crates/radix_connect/uniffi.toml @@ -0,0 +1,8 @@ +namespace = "radix_connect" + +[bindings.swift] +module_name = "SargonRadixConnect" +experimental_sendable_value_types = true + +[bindings.kotlin] +package_name = "com.radixdlt.sargon.radixconnect" diff --git a/crates/ret/Cargo.toml b/crates/ret/Cargo.toml new file mode 100644 index 000000000..17289ffa9 --- /dev/null +++ b/crates/ret/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "ret" +version = "1.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } +sargoncommon = { path = "../common" } +pretty_assertions = { workspace = true } +serde = { workspace = true } +serde_with = { workspace = true } +serde_json = { workspace = true } +strum = { workspace = true } +derive_more = { workspace = true } +enum-iterator = { workspace = true } +rand = { workspace = true } + +sbor = { workspace = true } +radix-rust = { workspace = true } +radix-engine = { workspace = true } +radix-common = { workspace = true } +radix-common-derive = { workspace = true } +radix-engine-interface = { workspace = true } +radix-transactions = { workspace = true } +radix-engine-toolkit-json = { workspace = true } +radix-engine-toolkit = { workspace = true } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } + +[lib] +crate-type = ["staticlib", "cdylib", "lib"] diff --git a/crates/ret/build.rs b/crates/ret/build.rs new file mode 100644 index 000000000..272332f1e --- /dev/null +++ b/crates/ret/build.rs @@ -0,0 +1,32 @@ +use std::env; +use std::path::Path; + +pub fn main() { + // Paths for reading fixtures used by tests + let fixtures_path = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../fixtures"); + println!("cargo:rustc-env=FIXTURES={}/", fixtures_path.display()); + let fixtures_transaction_path = fixtures_path.join("transaction"); + println!( + "cargo:rustc-env=FIXTURES_TX={}/", + fixtures_transaction_path.display() + ); + let fixtures_vector_path = fixtures_path.join("vector"); + println!( + "cargo:rustc-env=FIXTURES_VECTOR={}/", + fixtures_vector_path.display() + ); + let fixtures_models_path = fixtures_path.join("models"); + println!( + "cargo:rustc-env=FIXTURES_MODELS={}/", + fixtures_models_path.display() + ); + let fixtures_gw_models_path = fixtures_models_path.join("gateway"); + println!( + "cargo:rustc-env=FIXTURES_MODELS_GW={}/", + fixtures_gw_models_path.display() + ); + + uniffi::generate_scaffolding("src/ret.udl") + .expect("Should be able to build."); +} diff --git a/src/profile/v100/address/access_controller_address.rs b/crates/ret/src/address/access_controller_address.rs similarity index 88% rename from src/profile/v100/address/access_controller_address.rs rename to crates/ret/src/address/access_controller_address.rs index 75d539a97..72b26ffb3 100644 --- a/src/profile/v100/address/access_controller_address.rs +++ b/crates/ret/src/address/access_controller_address.rs @@ -85,23 +85,23 @@ mod tests { assert_eq!(format!("{:?}", a), s); } - #[test] - fn manual_perform_uniffi_conversion() { - type RetAddr = ::RetAddress; - let sut = SUT::sample(); - let bech32 = sut.to_string(); - let ret = RetAddr::try_from_bech32(&bech32).unwrap(); - - let ffi_side = - ::from_custom(ret); - assert_eq!(ffi_side, bech32); - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - assert_eq!(ret, from_ffi_side); - } + // #[test] + // fn manual_perform_uniffi_conversion() { + // type RetAddr = ::RetAddress; + // let sut = SUT::sample(); + // let bech32 = sut.to_string(); + // let ret = RetAddr::try_from_bech32(&bech32).unwrap(); + + // let ffi_side = + // ::from_custom(ret); + // assert_eq!(ffi_side, bech32); + // let from_ffi_side = + // ::into_custom( + // ffi_side, + // ) + // .unwrap(); + // assert_eq!(ret, from_ffi_side); + // } #[test] fn json_roundtrip() { diff --git a/src/profile/v100/address/access_controller_address_uniffi_fn.rs b/crates/ret/src/address/access_controller_address_uniffi_fn.rs similarity index 98% rename from src/profile/v100/address/access_controller_address_uniffi_fn.rs rename to crates/ret/src/address/access_controller_address_uniffi_fn.rs index a91ab7e6f..b112d5e9c 100644 --- a/src/profile/v100/address/access_controller_address_uniffi_fn.rs +++ b/crates/ret/src/address/access_controller_address_uniffi_fn.rs @@ -26,7 +26,6 @@ pub fn new_access_controller_address_sample_stokenet_other( #[cfg(test)] mod uniffi_tests { - use crate::prelude::*; use super::*; diff --git a/src/profile/v100/address/account_address.rs b/crates/ret/src/address/account_address.rs similarity index 91% rename from src/profile/v100/address/account_address.rs rename to crates/ret/src/address/account_address.rs index b2b04304a..60be776b5 100644 --- a/src/profile/v100/address/account_address.rs +++ b/crates/ret/src/address/account_address.rs @@ -26,6 +26,14 @@ decl_ret_wrapped_address!( account ); +impl Identifiable for AccountAddress { + type ID = Self; + + fn id(&self) -> Self::ID { + *self + } +} + impl AccountAddress { pub fn new( public_key: impl Into, @@ -338,32 +346,32 @@ mod tests { ); } - #[test] - fn manual_perform_uniffi_conversion_successful() { - type RetAddr = ::RetAddress; - let sut = SUT::sample(); - let bech32 = sut.to_string(); - let ret = RetAddr::try_from_bech32(&bech32).unwrap(); - - let ffi_side = - ::from_custom(ret); - assert_eq!(ffi_side, bech32); - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - assert_eq!(ret, from_ffi_side); - } - - #[test] - fn manual_perform_uniffi_conversion_fail() { - type RetAddr = ::RetAddress; - assert!(::into_custom( - "invalid".to_string() - ) - .is_err()); - } + // #[test] + // fn manual_perform_uniffi_conversion_successful() { + // type RetAddr = ::RetAddress; + // let sut = SUT::sample(); + // let bech32 = sut.to_string(); + // let ret = RetAddr::try_from_bech32(&bech32).unwrap(); + + // let ffi_side = + // ::from_custom(ret); + // assert_eq!(ffi_side, bech32); + // let from_ffi_side = + // ::into_custom( + // ffi_side, + // ) + // .unwrap(); + // assert_eq!(ret, from_ffi_side); + // } + + // #[test] + // fn manual_perform_uniffi_conversion_fail() { + // type RetAddr = ::RetAddress; + // assert!(::into_custom( + // "invalid".to_string() + // ) + // .is_err()); + // } #[test] fn json_roundtrip() { diff --git a/src/profile/v100/address/account_address_uniffi_fn.rs b/crates/ret/src/address/account_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/account_address_uniffi_fn.rs rename to crates/ret/src/address/account_address_uniffi_fn.rs diff --git a/src/profile/v100/address/address.rs b/crates/ret/src/address/address.rs similarity index 100% rename from src/profile/v100/address/address.rs rename to crates/ret/src/address/address.rs diff --git a/src/profile/v100/address/address_format.rs b/crates/ret/src/address/address_format.rs similarity index 90% rename from src/profile/v100/address/address_format.rs rename to crates/ret/src/address/address_format.rs index 62ddb5113..f1a4eae83 100644 --- a/src/profile/v100/address/address_format.rs +++ b/crates/ret/src/address/address_format.rs @@ -1,3 +1,5 @@ +use crate::prelude::*; + #[derive( Clone, Copy, diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/address_of_account_or_persona.rs b/crates/ret/src/address/address_of_account_or_persona.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/address_of_account_or_persona.rs rename to crates/ret/src/address/address_of_account_or_persona.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/address_union.rs b/crates/ret/src/address/address_union.rs similarity index 91% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/address_union.rs rename to crates/ret/src/address/address_union.rs index 6d28d9864..ff355f9e9 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/address_union.rs +++ b/crates/ret/src/address/address_union.rs @@ -75,43 +75,43 @@ macro_rules! address_union { } #[uniffi::export] - pub(crate) fn [< new_ $union_name:snake _sample_mainnet >]() -> $union_name { + pub fn [< new_ $union_name:snake _sample_mainnet >]() -> $union_name { $union_name::sample_mainnet() } #[uniffi::export] - pub(crate) fn [< new_ $union_name:snake _sample_mainnet_other >]() -> $union_name { + pub fn [< new_ $union_name:snake _sample_mainnet_other >]() -> $union_name { $union_name::sample_mainnet_other() } #[uniffi::export] - pub(crate) fn [< new_ $union_name:snake _sample_stokenet >]() -> $union_name { + pub fn [< new_ $union_name:snake _sample_stokenet >]() -> $union_name { $union_name::sample_stokenet() } #[uniffi::export] - pub(crate) fn [< new_ $union_name:snake _sample_stokenet_other >]() -> $union_name { + pub fn [< new_ $union_name:snake _sample_stokenet_other >]() -> $union_name { $union_name::sample_stokenet_other() } $( #[uniffi::export] - pub(crate) fn [< new_ $union_name:snake _sample_ $variant_name:snake _mainnet >]() -> $union_name { + pub fn [< new_ $union_name:snake _sample_ $variant_name:snake _mainnet >]() -> $union_name { $union_name::[]() } #[uniffi::export] - pub(crate) fn [< new_ $union_name:snake _sample_ $variant_name:snake _mainnet_other >]() -> $union_name { + pub fn [< new_ $union_name:snake _sample_ $variant_name:snake _mainnet_other >]() -> $union_name { $union_name::[]() } #[uniffi::export] - pub(crate) fn [< new_ $union_name:snake _sample_ $variant_name:snake _stokenet >]() -> $union_name { + pub fn [< new_ $union_name:snake _sample_ $variant_name:snake _stokenet >]() -> $union_name { $union_name::[]() } #[uniffi::export] - pub(crate) fn [< new_ $union_name:snake _sample_ $variant_name:snake _stokenet_other >]() -> $union_name { + pub fn [< new_ $union_name:snake _sample_ $variant_name:snake _stokenet_other >]() -> $union_name { $union_name::[]() } )+ @@ -384,36 +384,36 @@ macro_rules! address_union { #[allow(unused)] impl $union_name { $( - pub(crate) fn [< sample_ $variant_name:snake _mainnet >]() -> Self { + pub fn [< sample_ $variant_name:snake _mainnet >]() -> Self { Self::from($variant_type::sample_mainnet()) } - pub(crate) fn [< sample_ $variant_name:snake _mainnet_other >]() -> Self { + pub fn [< sample_ $variant_name:snake _mainnet_other >]() -> Self { Self::from($variant_type::sample_mainnet_other()) } - pub(crate) fn [< sample_ $variant_name:snake _stokenet >]() -> Self { + pub fn [< sample_ $variant_name:snake _stokenet >]() -> Self { Self::from($variant_type::sample_stokenet()) } - pub(crate) fn [< sample_ $variant_name:snake _stokenet_other >]() -> Self { + pub fn [< sample_ $variant_name:snake _stokenet_other >]() -> Self { Self::from($variant_type::sample_stokenet_other()) } )+ - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::sample_values_mainnet().into_iter().next().unwrap() } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::sample_values_mainnet().into_iter().rev().next().unwrap() } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::sample_values_stokenet().into_iter().next().unwrap() } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::sample_values_stokenet().into_iter().rev().next().unwrap() } @@ -462,3 +462,5 @@ macro_rules! address_union { } }; } + +pub(crate) use address_union; diff --git a/src/profile/v100/address/component_address.rs b/crates/ret/src/address/component_address.rs similarity index 88% rename from src/profile/v100/address/component_address.rs rename to crates/ret/src/address/component_address.rs index ee22bc082..7f174eba9 100644 --- a/src/profile/v100/address/component_address.rs +++ b/crates/ret/src/address/component_address.rs @@ -30,19 +30,19 @@ impl ComponentAddress { } impl ComponentAddress { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::sample_mainnet_global() } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::sample_mainnet_internal() } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::sample_stokenet_global() } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::sample_stokenet_internal() } } @@ -131,23 +131,23 @@ mod tests { assert!(!SUT::sample_mainnet_internal().is_global()); } - #[test] - fn manual_perform_uniffi_conversion() { - type RetAddr = ::RetAddress; - let sut = SUT::sample(); - let bech32 = sut.to_string(); - let ret = RetAddr::try_from_bech32(&bech32).unwrap(); - - let ffi_side = - ::from_custom(ret); - assert_eq!(ffi_side, bech32); - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - assert_eq!(ret, from_ffi_side); - } + // #[test] + // fn manual_perform_uniffi_conversion() { + // type RetAddr = ::RetAddress; + // let sut = SUT::sample(); + // let bech32 = sut.to_string(); + // let ret = RetAddr::try_from_bech32(&bech32).unwrap(); + + // let ffi_side = + // ::from_custom(ret); + // assert_eq!(ffi_side, bech32); + // let from_ffi_side = + // ::into_custom( + // ffi_side, + // ) + // .unwrap(); + // assert_eq!(ret, from_ffi_side); + // } #[test] fn display() { diff --git a/src/profile/v100/address/component_address_uniffi_fn.rs b/crates/ret/src/address/component_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/component_address_uniffi_fn.rs rename to crates/ret/src/address/component_address_uniffi_fn.rs diff --git a/src/profile/v100/address/entity_address.rs b/crates/ret/src/address/entity_address.rs similarity index 65% rename from src/profile/v100/address/entity_address.rs rename to crates/ret/src/address/entity_address.rs index 27950f984..d5806e8d5 100644 --- a/src/profile/v100/address/entity_address.rs +++ b/crates/ret/src/address/entity_address.rs @@ -12,7 +12,6 @@ pub trait EntityAddress: AddressViaRet { /// Creates a new address from `public_key` and `network_id` by bech32 encoding /// it. - #[cfg(not(tarpaulin_include))] // false negative fn from_public_key

(public_key: P, network_id: NetworkID) -> Self where P: Into + Clone, @@ -28,21 +27,4 @@ pub trait EntityAddress: AddressViaRet { Self::new(node_id, network_id).expect("To always be able to create a address from public key and network id.") } - - #[cfg(not(tarpaulin_include))] // false negative - fn from_hd_factor_instance_virtual_entity_creation< - E: IsEntityPath + Clone, - >( - hd_factor_instance_virtual_entity_creation: HDFactorInstanceTransactionSigning, - ) -> Self { - let network_id = - hd_factor_instance_virtual_entity_creation.path.network_id(); - - Self::from_public_key( - hd_factor_instance_virtual_entity_creation - .public_key() - .public_key, - network_id, - ) - } } diff --git a/src/profile/v100/address/identity_address.rs b/crates/ret/src/address/identity_address.rs similarity index 92% rename from src/profile/v100/address/identity_address.rs rename to crates/ret/src/address/identity_address.rs index 4e8bbb4a5..6a8017bb9 100644 --- a/src/profile/v100/address/identity_address.rs +++ b/crates/ret/src/address/identity_address.rs @@ -208,23 +208,23 @@ mod tests { ) } - #[test] - fn manual_perform_uniffi_conversion() { - type RetAddr = ::RetAddress; - let sut = SUT::sample(); - let bech32 = sut.to_string(); - let ret = RetAddr::try_from_bech32(&bech32).unwrap(); + // #[test] + // fn manual_perform_uniffi_conversion() { + // type RetAddr = ::RetAddress; + // let sut = SUT::sample(); + // let bech32 = sut.to_string(); + // let ret = RetAddr::try_from_bech32(&bech32).unwrap(); - let ffi_side = - ::from_custom(ret); - assert_eq!(ffi_side, bech32); - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - assert_eq!(ret, from_ffi_side); - } + // let ffi_side = + // ::from_custom(ret); + // assert_eq!(ffi_side, bech32); + // let from_ffi_side = + // ::into_custom( + // ffi_side, + // ) + // .unwrap(); + // assert_eq!(ret, from_ffi_side); + // } #[test] fn json_roundtrip_success() { diff --git a/src/profile/v100/address/identity_address_uniffi_fn.rs b/crates/ret/src/address/identity_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/identity_address_uniffi_fn.rs rename to crates/ret/src/address/identity_address_uniffi_fn.rs diff --git a/crates/ret/src/address/is_network_aware.rs b/crates/ret/src/address/is_network_aware.rs new file mode 100644 index 000000000..99f74fbfc --- /dev/null +++ b/crates/ret/src/address/is_network_aware.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; + +pub trait IsNetworkAware { + fn network_id(&self) -> NetworkID; + + /// Validates that `other` is on the same network as self. + fn is_on_same_network_as(&self, other: &impl IsNetworkAware) -> Result<()> { + let this = self.network_id(); + let other = other.network_id(); + if this != other { + Err(CommonError::NetworkDiscrepancy { + expected: this.discriminant(), + actual: other.discriminant(), + }) + } else { + Ok(()) + } + } +} diff --git a/src/profile/v100/address/legacy_olympia_account_address.rs b/crates/ret/src/address/legacy_olympia_account_address.rs similarity index 92% rename from src/profile/v100/address/legacy_olympia_account_address.rs rename to crates/ret/src/address/legacy_olympia_account_address.rs index 3d89b7077..f63c7a768 100644 --- a/src/profile/v100/address/legacy_olympia_account_address.rs +++ b/crates/ret/src/address/legacy_olympia_account_address.rs @@ -62,23 +62,12 @@ impl LegacyOlympiaAccountAddressSecretMagic { uniffi::custom_type!( LegacyOlympiaAccountAddressSecretMagic, - Secp256k1PublicKey -); - -/// UniFFI conversion for RET types which are DisplayFromStr using String as builtin. -impl crate::UniffiCustomTypeConverter - for LegacyOlympiaAccountAddressSecretMagic -{ - type Builtin = Secp256k1PublicKey; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(Self::from(val)) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.public_key + Secp256k1PublicKey, { + remote, + from_custom: |address| address.public_key, + try_into_custom: |val| Ok(Self::from(val)) } -} +); #[derive( Clone, @@ -313,15 +302,15 @@ mod tests { ); } - #[test] - fn manual_uniffi_conversion() { - let sut = SUT::sample_other().secret_magic; + // #[test] + // fn manual_uniffi_conversion() { + // let sut = SUT::sample_other().secret_magic; - let ffi = ::from_custom(sut); - assert_eq!(ffi, sut.public_key); - let from_ffi = ::into_custom(ffi).unwrap(); - assert_eq!(from_ffi, sut); - } + // let ffi = ::from_custom(sut); + // assert_eq!(ffi, sut.public_key); + // let from_ffi = ::into_custom(ffi).unwrap(); + // assert_eq!(from_ffi, sut); + // } #[test] fn json_roundtrip() { diff --git a/src/profile/v100/address/legacy_olympia_account_address_uniffi_fn.rs b/crates/ret/src/address/legacy_olympia_account_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/legacy_olympia_account_address_uniffi_fn.rs rename to crates/ret/src/address/legacy_olympia_account_address_uniffi_fn.rs diff --git a/src/profile/v100/address/mod.rs b/crates/ret/src/address/mod.rs similarity index 91% rename from src/profile/v100/address/mod.rs rename to crates/ret/src/address/mod.rs index 29209913a..aeb9fbdcb 100644 --- a/src/profile/v100/address/mod.rs +++ b/crates/ret/src/address/mod.rs @@ -4,6 +4,12 @@ mod account_address; mod account_address_uniffi_fn; mod address; mod address_format; +mod address_of_account_or_persona; +mod is_network_aware; + +#[macro_use] +mod address_union; + mod component_address; mod component_address_uniffi_fn; mod entity_address; @@ -36,11 +42,14 @@ pub use account_address::*; pub use account_address_uniffi_fn::*; pub use address::*; pub use address_format::*; +pub use address_of_account_or_persona::*; +pub(crate) use address_union::*; pub use component_address::*; pub use component_address_uniffi_fn::*; pub use entity_address::*; pub use identity_address::*; pub use identity_address_uniffi_fn::*; +pub use is_network_aware::*; pub use legacy_olympia_account_address::*; pub use legacy_olympia_account_address_uniffi_fn::*; pub use non_fungible_global_id::*; diff --git a/src/profile/v100/address/non_fungible_global_id.rs b/crates/ret/src/address/non_fungible_global_id.rs similarity index 98% rename from src/profile/v100/address/non_fungible_global_id.rs rename to crates/ret/src/address/non_fungible_global_id.rs index 2f292c2b0..c11d314f7 100644 --- a/src/profile/v100/address/non_fungible_global_id.rs +++ b/crates/ret/src/address/non_fungible_global_id.rs @@ -23,8 +23,8 @@ pub struct NonFungibleGlobalId { // // For more info see slack: // https://rdxworks.slack.com/archives/C01HK4QFXNY/p1709633826502809?thread_ts=1709633374.199459&channel=C01HK4QFXNY&message_ts=1709633826.502809 - pub(crate) resource_address: ResourceAddress, - pub(crate) non_fungible_local_id: NonFungibleLocalId, + pub resource_address: ResourceAddress, + pub non_fungible_local_id: NonFungibleLocalId, } impl NonFungibleGlobalId { @@ -39,7 +39,7 @@ impl NonFungibleGlobalId { ) -> Self { let resource_address = resource_address.into(); if resource_address.is_fungible() { - info!("Notice: Fungible resource address used with NonFungible Global ID.") + debug!("Notice: Fungible resource address used with NonFungible Global ID.") } Self { resource_address, @@ -198,7 +198,7 @@ impl HasSampleValues for NonFungibleGlobalId { impl NonFungibleGlobalId { #[allow(unused)] - pub(crate) fn sample_ruid() -> Self { + pub fn sample_ruid() -> Self { Self::new( NonFungibleResourceAddress::sample(), NonFungibleLocalId::ruid( diff --git a/src/profile/v100/address/non_fungible_global_id_uniffi_fn.rs b/crates/ret/src/address/non_fungible_global_id_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/non_fungible_global_id_uniffi_fn.rs rename to crates/ret/src/address/non_fungible_global_id_uniffi_fn.rs diff --git a/src/profile/v100/address/non_fungible_local_id.rs b/crates/ret/src/address/non_fungible_local_id.rs similarity index 97% rename from src/profile/v100/address/non_fungible_local_id.rs rename to crates/ret/src/address/non_fungible_local_id.rs index 9b4b131ee..1704d5822 100644 --- a/src/profile/v100/address/non_fungible_local_id.rs +++ b/crates/ret/src/address/non_fungible_local_id.rs @@ -46,7 +46,7 @@ impl NonFungibleLocalId { } impl NonFungibleLocalId { - pub(crate) fn random() -> Self { + pub fn random() -> Self { Self::Bytes { value: NonEmptyMax64Bytes::generate(), } @@ -92,12 +92,6 @@ impl std::fmt::Display for NonFungibleLocalId { } } -impl From for NonEmptyMax64Bytes { - fn from(value: ScryptoBytesNonFungibleLocalId) -> Self { - Self::try_from(value.value()).expect("Should not be possible, since ScryptoBytesNonFungibleLocalId have validated length") - } -} - impl From for NonFungibleLocalId { fn from(value: ScryptoNonFungibleLocalId) -> Self { match value { diff --git a/src/profile/v100/address/non_fungible_local_id_string.rs b/crates/ret/src/address/non_fungible_local_id_string.rs similarity index 85% rename from src/profile/v100/address/non_fungible_local_id_string.rs rename to crates/ret/src/address/non_fungible_local_id_string.rs index b40b445a9..7f7ef4ea7 100644 --- a/src/profile/v100/address/non_fungible_local_id_string.rs +++ b/crates/ret/src/address/non_fungible_local_id_string.rs @@ -64,21 +64,25 @@ fn scrypto_string_non_fungible_local_id( }) } -uniffi::custom_type!(ScryptoStringNonFungibleLocalId, String); - -impl crate::UniffiCustomTypeConverter for ScryptoStringNonFungibleLocalId { - type Builtin = String; - - #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests - fn into_custom(val: Self::Builtin) -> uniffi::Result { - scrypto_string_non_fungible_local_id(val).map_err(|e| e.into()) - } - - #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests - fn from_custom(obj: Self) -> Self::Builtin { - obj.value().to_owned() - } -} +uniffi::custom_type!(ScryptoStringNonFungibleLocalId, String, { + remote, + from_custom: |id| id.value().to_owned(), + try_into_custom: |val| scrypto_string_non_fungible_local_id(val).map_err(|e| e.into()) +}); + +// impl crate::UniffiCustomTypeConverter for ScryptoStringNonFungibleLocalId { +// type Builtin = String; + +// #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests +// fn into_custom(val: Self::Builtin) -> uniffi::Result { +// +// } + +// #[cfg(not(tarpaulin_include))] // false negative, tested in bindgen tests +// fn from_custom(obj: Self) -> Self::Builtin { +// obj.value().to_owned() +// } +// } #[cfg(test)] mod tests { diff --git a/src/profile/v100/address/non_fungible_local_id_uniffi_fn.rs b/crates/ret/src/address/non_fungible_local_id_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/non_fungible_local_id_uniffi_fn.rs rename to crates/ret/src/address/non_fungible_local_id_uniffi_fn.rs diff --git a/src/profile/v100/address/non_fungible_resource_address.rs b/crates/ret/src/address/non_fungible_resource_address.rs similarity index 94% rename from src/profile/v100/address/non_fungible_resource_address.rs rename to crates/ret/src/address/non_fungible_resource_address.rs index 167353dfe..bd427dc51 100644 --- a/src/profile/v100/address/non_fungible_resource_address.rs +++ b/crates/ret/src/address/non_fungible_resource_address.rs @@ -109,7 +109,7 @@ macro_rules! decl_specialized_address { } } - #[cfg(test)] + // #[cfg(test)] // FIXME impl From<&str> for $specialized_address_type { /// TEST ONLY fn from(value: &str) -> Self { @@ -279,23 +279,23 @@ mod tests { assert_eq!(format!("{:?}", a), s); } - #[test] - fn manual_perform_uniffi_conversion() { - type RetAddr = ::RetAddress; - let sut = SUT::sample(); - let bech32 = sut.to_string(); - let ret = RetAddr::try_from_bech32(&bech32).unwrap(); - - let ffi_side = - ::from_custom(ret); - assert_eq!(ffi_side, bech32); - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - assert_eq!(ret, from_ffi_side); - } + // #[test] + // fn manual_perform_uniffi_conversion() { + // type RetAddr = ::RetAddress; + // let sut = SUT::sample(); + // let bech32 = sut.to_string(); + // let ret = RetAddr::try_from_bech32(&bech32).unwrap(); + + // let ffi_side = + // ::from_custom(ret); + // assert_eq!(ffi_side, bech32); + // let from_ffi_side = + // ::into_custom( + // ffi_side, + // ) + // .unwrap(); + // assert_eq!(ret, from_ffi_side); + // } #[test] fn json_roundtrip() { diff --git a/src/profile/v100/address/non_fungible_resource_address_uniffi_fn.rs b/crates/ret/src/address/non_fungible_resource_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/non_fungible_resource_address_uniffi_fn.rs rename to crates/ret/src/address/non_fungible_resource_address_uniffi_fn.rs diff --git a/src/profile/v100/address/package_address.rs b/crates/ret/src/address/package_address.rs similarity index 85% rename from src/profile/v100/address/package_address.rs rename to crates/ret/src/address/package_address.rs index 2897e09ef..8a34e87fd 100644 --- a/src/profile/v100/address/package_address.rs +++ b/crates/ret/src/address/package_address.rs @@ -15,15 +15,15 @@ decl_ret_wrapped_address!( ); impl PackageAddress { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::sample_mainnet_faucet() } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::sample_mainnet_royalty() } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::sample_stokenet_gumball_club() } } @@ -82,23 +82,23 @@ mod tests { assert_ne!(SUT::sample(), SUT::sample_other()); } - #[test] - fn manual_perform_uniffi_conversion() { - type RetAddr = ::RetAddress; - let sut = SUT::sample(); - let bech32 = sut.to_string(); - let ret = RetAddr::try_from_bech32(&bech32).unwrap(); - - let ffi_side = - ::from_custom(ret); - assert_eq!(ffi_side, bech32); - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - assert_eq!(ret, from_ffi_side); - } + // #[test] + // fn manual_perform_uniffi_conversion() { + // type RetAddr = ::RetAddress; + // let sut = SUT::sample(); + // let bech32 = sut.to_string(); + // let ret = RetAddr::try_from_bech32(&bech32).unwrap(); + + // let ffi_side = + // ::from_custom(ret); + // assert_eq!(ffi_side, bech32); + // let from_ffi_side = + // ::into_custom( + // ffi_side, + // ) + // .unwrap(); + // assert_eq!(ret, from_ffi_side); + // } #[test] fn display() { diff --git a/src/profile/v100/address/package_address_uniffi_fn.rs b/crates/ret/src/address/package_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/package_address_uniffi_fn.rs rename to crates/ret/src/address/package_address_uniffi_fn.rs diff --git a/src/profile/v100/address/pool_address.rs b/crates/ret/src/address/pool_address.rs similarity index 90% rename from src/profile/v100/address/pool_address.rs rename to crates/ret/src/address/pool_address.rs index 3ac8cbcdd..cc2f8aafc 100644 --- a/src/profile/v100/address/pool_address.rs +++ b/crates/ret/src/address/pool_address.rs @@ -21,19 +21,19 @@ decl_ret_wrapped_address!( ); impl PoolAddress { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::sample_mainnet_single_pool() } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::sample_mainnet_bi_pool() } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::sample_stokenet_single_pool() } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::sample_stokenet_bi_pool() } } @@ -177,23 +177,23 @@ mod tests { assert_eq!(format!("{:?}", a), s); } - #[test] - fn manual_perform_uniffi_conversion() { - type RetAddr = ::RetAddress; - let sut = SUT::sample(); - let bech32 = sut.to_string(); - let ret = RetAddr::try_from_bech32(&bech32).unwrap(); - - let ffi_side = - ::from_custom(ret); - assert_eq!(ffi_side, bech32); - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - assert_eq!(ret, from_ffi_side); - } + // #[test] + // fn manual_perform_uniffi_conversion() { + // type RetAddr = ::RetAddress; + // let sut = SUT::sample(); + // let bech32 = sut.to_string(); + // let ret = RetAddr::try_from_bech32(&bech32).unwrap(); + + // let ffi_side = + // ::from_custom(ret); + // assert_eq!(ffi_side, bech32); + // let from_ffi_side = + // ::into_custom( + // ffi_side, + // ) + // .unwrap(); + // assert_eq!(ret, from_ffi_side); + // } #[test] fn json_roundtrip() { diff --git a/src/profile/v100/address/pool_address_uniffi_fn.rs b/crates/ret/src/address/pool_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/pool_address_uniffi_fn.rs rename to crates/ret/src/address/pool_address_uniffi_fn.rs diff --git a/src/profile/v100/address/resource_address.rs b/crates/ret/src/address/resource_address.rs similarity index 88% rename from src/profile/v100/address/resource_address.rs rename to crates/ret/src/address/resource_address.rs index 8ab11e458..289834a5f 100644 --- a/src/profile/v100/address/resource_address.rs +++ b/crates/ret/src/address/resource_address.rs @@ -49,88 +49,88 @@ impl HasSampleValues for ResourceAddress { #[allow(unused)] impl ResourceAddress { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::sample_mainnet_xrd() } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::sample_mainnet_candy() } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::sample_stokenet_xrd() } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::sample_stokenet_gum() } } impl ResourceAddress { /// The RAD on mainnet - pub(crate) fn sample_mainnet_xrd() -> Self { + pub fn sample_mainnet_xrd() -> Self { "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" .parse() .expect("XRD") } /// Candy by Gumball club on mainnet - pub(crate) fn sample_mainnet_candy() -> Self { + pub fn sample_mainnet_candy() -> Self { "resource_rdx1t4dy69k6s0gv040xa64cyadyefwtett62ng6xfdnljyydnml7t6g3j" .parse() .expect("Candy") } - pub(crate) fn sample_mainnet_nft_gc_membership() -> Self { + pub fn sample_mainnet_nft_gc_membership() -> Self { "resource_rdx1nfyg2f68jw7hfdlg5hzvd8ylsa7e0kjl68t5t62v3ttamtejc9wlxa" .parse() .expect("GC Membership") } - pub(crate) fn sample_mainnet_nft_other() -> Self { + pub fn sample_mainnet_nft_other() -> Self { "resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd" .parse() .expect("Valid Scorpion NFT Global ID") } - pub(crate) fn sample_stokenet_xrd() -> Self { + pub fn sample_stokenet_xrd() -> Self { "resource_tdx_2_1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxtfd2jc" .parse() .expect("XRD") } - pub(crate) fn sample_stokenet_gum() -> Self { + pub fn sample_stokenet_gum() -> Self { "resource_tdx_2_1t4kep9ldg9t0cszj78z6fcr2zvfxfq7muetq7pyvhdtctwxum90scq" .parse() .expect("Gum") } - pub(crate) fn sample_stokenet_gc_tokens() -> Self { + pub fn sample_stokenet_gc_tokens() -> Self { "resource_tdx_2_1thqcgjw37fjgycpvqr52nx4jcsdeuq75mf2nywme07kzsuds9a4psp" .parse() .expect("GC Tokens") } - pub(crate) fn sample_stokenet_candy() -> Self { + pub fn sample_stokenet_candy() -> Self { "resource_tdx_2_1tk30vj4ene95e3vhymtf2p35fzl29rv4us36capu2rz0vretw9gzr3" .parse() .expect("Candy") } - pub(crate) fn sample_stokenet_nft_gc_membership() -> Self { + pub fn sample_stokenet_nft_gc_membership() -> Self { "resource_tdx_2_1ng88qk08hrgmad30rzdxpyx779yuta4cwcjc3gstk60jhachsv94g9" .parse() .expect("GC membership") } - pub(crate) fn sample_stokenet_nft_other() -> Self { + pub fn sample_stokenet_nft_other() -> Self { "resource_tdx_2_1ngw6cufaxs5p82kw49juy2yfkt53se76vr0xfsu3tvyduuw6s0y6lc" .parse() .expect("valid sample value") } #[allow(unused)] - pub(crate) fn sample_sim_xrd() -> Self { + pub fn sample_sim_xrd() -> Self { "resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3" .parse() .expect("valid sample value") @@ -323,23 +323,23 @@ mod tests { assert_eq!(format!("{:?}", a), s); } - #[test] - fn manual_perform_uniffi_conversion() { - type RetAddr = ::RetAddress; - let sut = SUT::sample(); - let bech32 = sut.to_string(); - let ret = RetAddr::try_from_bech32(&bech32).unwrap(); - - let ffi_side = - ::from_custom(ret); - assert_eq!(ffi_side, bech32); - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - assert_eq!(ret, from_ffi_side); - } + // #[test] + // fn manual_perform_uniffi_conversion() { + // type RetAddr = ::RetAddress; + // let sut = SUT::sample(); + // let bech32 = sut.to_string(); + // let ret = RetAddr::try_from_bech32(&bech32).unwrap(); + + // let ffi_side = + // ::from_custom(ret); + // assert_eq!(ffi_side, bech32); + // let from_ffi_side = + // ::into_custom( + // ffi_side, + // ) + // .unwrap(); + // assert_eq!(ret, from_ffi_side); + // } #[test] fn json_roundtrip() { diff --git a/src/profile/v100/address/resource_address_uniffi_fn.rs b/crates/ret/src/address/resource_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/resource_address_uniffi_fn.rs rename to crates/ret/src/address/resource_address_uniffi_fn.rs diff --git a/src/profile/v100/address/validator_address.rs b/crates/ret/src/address/validator_address.rs similarity index 87% rename from src/profile/v100/address/validator_address.rs rename to crates/ret/src/address/validator_address.rs index ec70db860..d7c590a6a 100644 --- a/src/profile/v100/address/validator_address.rs +++ b/crates/ret/src/address/validator_address.rs @@ -84,23 +84,23 @@ mod tests { assert_eq!(format!("{:?}", a), s); } - #[test] - fn manual_perform_uniffi_conversion() { - type RetAddr = ::RetAddress; - let sut = SUT::sample(); - let bech32 = sut.to_string(); - let ret = RetAddr::try_from_bech32(&bech32).unwrap(); - - let ffi_side = - ::from_custom(ret); - assert_eq!(ffi_side, bech32); - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - assert_eq!(ret, from_ffi_side); - } + // #[test] + // fn manual_perform_uniffi_conversion() { + // type RetAddr = ::RetAddress; + // let sut = SUT::sample(); + // let bech32 = sut.to_string(); + // let ret = RetAddr::try_from_bech32(&bech32).unwrap(); + + // let ffi_side = + // ::from_custom(ret); + // assert_eq!(ffi_side, bech32); + // let from_ffi_side = + // ::into_custom( + // ffi_side, + // ) + // .unwrap(); + // assert_eq!(ret, from_ffi_side); + // } #[test] fn json_roundtrip() { diff --git a/src/profile/v100/address/validator_address_uniffi_fn.rs b/crates/ret/src/address/validator_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/validator_address_uniffi_fn.rs rename to crates/ret/src/address/validator_address_uniffi_fn.rs diff --git a/src/profile/v100/address/vault_address.rs b/crates/ret/src/address/vault_address.rs similarity index 86% rename from src/profile/v100/address/vault_address.rs rename to crates/ret/src/address/vault_address.rs index 3905bab75..0fb790b7f 100644 --- a/src/profile/v100/address/vault_address.rs +++ b/crates/ret/src/address/vault_address.rs @@ -28,19 +28,19 @@ impl VaultAddress { } impl VaultAddress { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::sample_mainnet_fungible() } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::sample_mainnet_non_fungible() } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::sample_stokenet_fungible() } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::sample_stokenet_non_fungible() } } @@ -114,23 +114,23 @@ mod tests { assert_eq!(format!("{:?}", a), s); } - #[test] - fn manual_perform_uniffi_conversion() { - type RetAddr = ::RetAddress; - let sut = SUT::sample(); - let bech32 = sut.to_string(); - let ret = RetAddr::try_from_bech32(&bech32).unwrap(); - - let ffi_side = - ::from_custom(ret); - assert_eq!(ffi_side, bech32); - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - assert_eq!(ret, from_ffi_side); - } + // #[test] + // fn manual_perform_uniffi_conversion() { + // type RetAddr = ::RetAddress; + // let sut = SUT::sample(); + // let bech32 = sut.to_string(); + // let ret = RetAddr::try_from_bech32(&bech32).unwrap(); + + // let ffi_side = + // ::from_custom(ret); + // assert_eq!(ffi_side, bech32); + // let from_ffi_side = + // ::into_custom( + // ffi_side, + // ) + // .unwrap(); + // assert_eq!(ret, from_ffi_side); + // } #[test] fn json_roundtrip() { diff --git a/src/profile/v100/address/vault_address_uniffi_fn.rs b/crates/ret/src/address/vault_address_uniffi_fn.rs similarity index 100% rename from src/profile/v100/address/vault_address_uniffi_fn.rs rename to crates/ret/src/address/vault_address_uniffi_fn.rs diff --git a/src/profile/v100/address/wrap_ret_address.rs b/crates/ret/src/address/wrap_ret_address.rs similarity index 89% rename from src/profile/v100/address/wrap_ret_address.rs rename to crates/ret/src/address/wrap_ret_address.rs index 5e55cb5e9..78fe0c0cf 100644 --- a/src/profile/v100/address/wrap_ret_address.rs +++ b/crates/ret/src/address/wrap_ret_address.rs @@ -7,23 +7,6 @@ pub trait AddressViaRet: Sized { ) -> Result; } -pub trait IsNetworkAware { - fn network_id(&self) -> NetworkID; - - fn is_on_same_network_as(&self, other: &impl IsNetworkAware) -> Result<()> { - let this = self.network_id(); - let other = other.network_id(); - if this != other { - Err(CommonError::NetworkDiscrepancy { - expected: this, - actual: other, - }) - } else { - Ok(()) - } - } -} - pub trait IsAddress: IsNetworkAware + Serialize @@ -36,26 +19,18 @@ pub trait IsAddress: /// Helps with unit testing, so that we do not need to explicitly specify each /// (Sargon) Address types corresponding RET address type, but can use, e.g. /// `AccountAddress::RetAddress` instead of `radix_engine_toolkit::models::canonical_address_types::CanonicalAccountAddress` -pub(crate) trait FromRetAddress { +pub trait FromRetAddress { type RetAddress; } -pub(crate) fn format_string( - s: impl AsRef, - start: usize, - end: usize, -) -> String { +pub fn format_string(s: impl AsRef, start: usize, end: usize) -> String { let s = s.as_ref(); let prefix = &s[0..start]; let suffix = suffix_str(end, s); format!("{}...{}", prefix, suffix) } -pub(crate) fn trim_string( - s: impl AsRef, - prefix: usize, - suffix: usize, -) -> String { +pub fn trim_string(s: impl AsRef, prefix: usize, suffix: usize) -> String { let s = s.as_ref(); let start = prefix; let end = s.len() - suffix; @@ -94,7 +69,7 @@ macro_rules! decl_ret_wrapped_address { #[display("{secret_magic}")] #[debug("{secret_magic}")] pub struct [< $address_type:camel Address >] { - pub(crate) secret_magic: [< Ret $address_type:camel Address >], // Do NOT add comments above + pub secret_magic: [< Ret $address_type:camel Address >], // Do NOT add comments above } #[uniffi::export] @@ -131,23 +106,29 @@ macro_rules! decl_ret_wrapped_address { [<$address_type:camel Address >]::random(network_id) } - uniffi::custom_type!([< Ret $address_type:camel Address >], String); - - /// UniFFI conversion for RET types which are DisplayFromStr using String as builtin. - impl crate::UniffiCustomTypeConverter for [< Ret $address_type:camel Address >] { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { + uniffi::custom_type!([< Ret $address_type:camel Address >], String, { + remote, + from_custom: |addr| addr.to_string(), + try_into_custom: |val| { val.parse::() .map_err(|_| { CommonError::FailedToDecodeAddressFromBech32 { bad_value: val }.into() }) } + }); - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_string() - } - } + // /// UniFFI conversion for RET types which are DisplayFromStr using String as builtin. + // impl crate::UniffiCustomTypeConverter for [< Ret $address_type:camel Address >] { + // type Builtin = String; + + // fn into_custom(val: Self::Builtin) -> uniffi::Result { + + // } + + // fn from_custom(obj: Self) -> Self::Builtin { + // obj.to_string() + // } + // } impl From<[< Ret $address_type:camel Address >]> for [< $address_type:camel Address >] { fn from(value: [< Ret $address_type:camel Address >]) -> Self { @@ -162,7 +143,8 @@ macro_rules! decl_ret_wrapped_address { } } - #[cfg(test)] + // Actually we want `#[cfg(test)]` but does not work across + // crates - FIXME! impl From<&str> for [< $address_type:camel Address >] { /// TEST ONLY fn from(value: &str) -> Self { @@ -222,11 +204,11 @@ macro_rules! decl_ret_wrapped_address { } } - pub(crate) fn scrypto(&self) -> ScryptoGlobalAddress { + pub fn scrypto(&self) -> ScryptoGlobalAddress { ScryptoGlobalAddress::try_from(self.node_id()) .expect("Should always be able to convert a Sargon Address into radix engine 'GlobalAddress'.") } - pub(crate) fn node_id(&self) -> ScryptoNodeId { + pub fn node_id(&self) -> ScryptoNodeId { self.secret_magic.node_id() } @@ -348,7 +330,10 @@ macro_rules! decl_ret_wrapped_address { [< Ret $address_type:camel Address >]::new(node_id.clone(), network_id.discriminant()) .map_err(|e| { error!("Failed create address, from node and network_id, RET error: {:?}", e); - CommonError::FailedToCreateAddressViaRetAddressFromNodeIdAndNetworkID { node_id_as_hex: node_id.to_hex(), network_id } + CommonError::FailedToCreateAddressViaRetAddressFromNodeIdAndNetworkID { + node_id_as_hex: node_id.to_hex(), + network_id: network_id.discriminant() + } }) .map(|i| [< $address_type:camel Address >]::from(i)) } @@ -382,7 +367,7 @@ mod tests { ::new(unknown_node_id, NetworkID::Mainnet), Err(CommonError::FailedToCreateAddressViaRetAddressFromNodeIdAndNetworkID { node_id_as_hex: "deffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_owned(), - network_id: NetworkID::Mainnet, + network_id: 1, }) ); } diff --git a/crates/ret/src/is_intent_signing/is_intent_signing.rs b/crates/ret/src/is_intent_signing/is_intent_signing.rs new file mode 100644 index 000000000..6e9b8b7d7 --- /dev/null +++ b/crates/ret/src/is_intent_signing/is_intent_signing.rs @@ -0,0 +1,60 @@ +use crate::prelude::*; + +pub trait IsIntentSigning>: + IsPrivateKey

+{ + fn sign_intent_hash(&self, intent_hash: &IntentHash) -> IntentSignature + where + (P, Self::Signature): Into, + { + let public_key: P = self.public_key(); + let signature = self.sign(&intent_hash.hash); + let tuple: SignatureWithPublicKey = (public_key, signature).into(); + tuple.into() + } + + fn notarize_hash( + &self, + signed_intent_hash: &SignedIntentHash, + ) -> NotarySignature + where + Self::Signature: Into, + { + self.sign(&signed_intent_hash.hash).into() + } +} + +impl IsIntentSigning for Ed25519PrivateKey {} +impl IsIntentSigning for Secp256k1PrivateKey {} + +pub trait HashSigning { + fn sign_intent_hash(&self, intent_hash: &IntentHash) -> IntentSignature; + + fn notarize_hash( + &self, + signed_intent_hash: &SignedIntentHash, + ) -> NotarySignature; +} + +impl HashSigning for PrivateKey { + fn sign_intent_hash(&self, intent_hash: &IntentHash) -> IntentSignature { + match self { + PrivateKey::Ed25519(key) => SignatureWithPublicKey::Ed25519 { + public_key: key.public_key(), + signature: key.sign(&intent_hash.hash), + }, + PrivateKey::Secp256k1(key) => SignatureWithPublicKey::Secp256k1 { + public_key: key.public_key(), + signature: key.sign(&intent_hash.hash), + }, + } + .into() + } + + fn notarize_hash( + &self, + signed_intent_hash: &SignedIntentHash, + ) -> NotarySignature { + self.sign(&signed_intent_hash.hash).into() + } +} diff --git a/crates/ret/src/is_intent_signing/mod.rs b/crates/ret/src/is_intent_signing/mod.rs new file mode 100644 index 000000000..fd1342503 --- /dev/null +++ b/crates/ret/src/is_intent_signing/mod.rs @@ -0,0 +1,3 @@ +mod is_intent_signing; + +pub use is_intent_signing::*; diff --git a/crates/ret/src/lib.rs b/crates/ret/src/lib.rs new file mode 100644 index 000000000..aeed45138 --- /dev/null +++ b/crates/ret/src/lib.rs @@ -0,0 +1,15 @@ +mod address; +mod is_intent_signing; +mod low_level; + +pub mod prelude { + pub use crate::address::*; + pub use crate::is_intent_signing::*; + pub use crate::low_level::*; + + pub use sargoncommon::prelude::*; +} + +pub use prelude::*; + +uniffi::include_scaffolding!("ret"); diff --git a/crates/ret/src/low_level/address_conversion/mod.rs b/crates/ret/src/low_level/address_conversion/mod.rs new file mode 100644 index 000000000..5c6a85697 --- /dev/null +++ b/crates/ret/src/low_level/address_conversion/mod.rs @@ -0,0 +1 @@ +mod resource_address_from; diff --git a/src/wrapped_radix_engine_toolkit/low_level/address_conversion/resource_address_from.rs b/crates/ret/src/low_level/address_conversion/resource_address_from.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/address_conversion/resource_address_from.rs rename to crates/ret/src/low_level/address_conversion/resource_address_from.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/addresses_manifest_builder_support.rs b/crates/ret/src/low_level/addresses_manifest_builder_support.rs similarity index 98% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/addresses_manifest_builder_support.rs rename to crates/ret/src/low_level/addresses_manifest_builder_support.rs index c3156328f..b28538e41 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/addresses_manifest_builder_support.rs +++ b/crates/ret/src/low_level/addresses_manifest_builder_support.rs @@ -125,7 +125,7 @@ impl From for ScryptoResourceOrNonFungible { } #[cfg(not(tarpaulin_include))] // false negative, tested. -pub(crate) fn to_vec_network_aware( +pub fn to_vec_network_aware( values: impl IntoIterator, network_id: NetworkID, ) -> Vec @@ -139,7 +139,7 @@ where .collect_vec() } -pub(crate) fn to_hashmap_network_aware_key( +pub fn to_hashmap_network_aware_key( values: impl IntoIterator, network_id: NetworkID, ) -> HashMap @@ -154,7 +154,7 @@ where } #[cfg(not(tarpaulin_include))] // false negative, tested. -pub(crate) fn filter_try_to_vec_network_aware( +pub fn filter_try_to_vec_network_aware( values: impl IntoIterator, network_id: NetworkID, ) -> Vec diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/assert_manifest.rs b/crates/ret/src/low_level/assert_manifest.rs similarity index 94% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/assert_manifest.rs rename to crates/ret/src/low_level/assert_manifest.rs index e98093bc3..22698bb54 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/assert_manifest.rs +++ b/crates/ret/src/low_level/assert_manifest.rs @@ -17,7 +17,8 @@ use crate::prelude::*; /// string without this test method since every space need to be correct. /// This function also allows for prettier formatting of the manifest string /// we assert against, since we can use any number of tabs. -#[cfg(test)] +/// +/// FIXME: We want this to be `#[uniffi(test)]` but that hides it across crates.. pub fn manifest_eq(manifest: TransactionManifest, expected: impl AsRef) { let trim = |s: &str| s.replace(' ', "").replace('\t', " ").trim().to_owned(); @@ -44,7 +45,8 @@ pub fn manifest_eq(manifest: TransactionManifest, expected: impl AsRef) { /// string without this test method since every space need to be correct. /// This function also allows for prettier formatting of the Instructions set string /// we assert against, since we can use any number of tabs. -#[cfg(test)] +/// +/// FIXME: We want this to be `#[uniffi(test)]` but that hides it across crates.. pub fn instructions_eq(instructions: Instructions, expected: impl AsRef) { let trim = |s: &str| s.replace(' ', "").replace('\t', " ").trim().to_owned(); diff --git a/src/wrapped_radix_engine_toolkit/low_level/compiled_notarized_intent.rs b/crates/ret/src/low_level/compiled_notarized_intent.rs similarity index 97% rename from src/wrapped_radix_engine_toolkit/low_level/compiled_notarized_intent.rs rename to crates/ret/src/low_level/compiled_notarized_intent.rs index 8a72f48e5..c2fbc5a45 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/compiled_notarized_intent.rs +++ b/crates/ret/src/low_level/compiled_notarized_intent.rs @@ -1,5 +1,8 @@ use crate::prelude::*; +#[allow(unused)] +use sbor::ValueKind as ScryptoValueKind; + #[derive( Serialize, Deserialize, @@ -37,7 +40,7 @@ impl CompiledNotarizedIntent { } } -pub(crate) fn compile_notarized_intent( +pub fn compile_notarized_intent( scrypto_notarized_intent: ScryptoNotarizedTransaction, ) -> Result { RET_compile_notarized_tx(&scrypto_notarized_intent) @@ -73,9 +76,8 @@ impl HasSampleValues for CompiledNotarizedIntent { } } -use sbor::ValueKind as ScryptoValueKind; #[cfg(test)] -pub(crate) fn invalid_signed_intent() -> ScryptoSignedIntent { +pub fn invalid_signed_intent() -> ScryptoSignedIntent { let invalid_value = ScryptoManifestValue::Tuple { fields: vec![ScryptoManifestValue::Array { element_value_kind: ScryptoValueKind::U8, @@ -168,7 +170,7 @@ mod tests { }); assert_eq!( res, - Err(CommonError::InvalidNotarizedIntentFailedToEncode { underlying: "MismatchingArrayElementValueKind { element_value_kind: 7, actual_value_kind: 8 }".to_owned() }) + Err(CommonError::InvalidNotarizedIntentFailedToEncode { underlying: "MismatchingArrayElementValueKind { element_value_kind: 7, actual_value_kind: 8 }".to_owned() }) ); } } diff --git a/src/wrapped_radix_engine_toolkit/low_level/compiled_notarized_intent_uniffi_fn.rs b/crates/ret/src/low_level/compiled_notarized_intent_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/compiled_notarized_intent_uniffi_fn.rs rename to crates/ret/src/low_level/compiled_notarized_intent_uniffi_fn.rs diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/deposit_rule.rs b/crates/ret/src/low_level/deposit_rule.rs similarity index 98% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/deposit_rule.rs rename to crates/ret/src/low_level/deposit_rule.rs index 498b321e9..4f3efe0f1 100644 --- a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/deposit_rule.rs +++ b/crates/ret/src/low_level/deposit_rule.rs @@ -1,5 +1,7 @@ use crate::prelude::*; +json_string_convertible!(DepositRule); + /// The general deposit rule to apply #[derive( Serialize, diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/deposit_rule_uniffi_fn.rs b/crates/ret/src/low_level/deposit_rule_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/deposit_rule_uniffi_fn.rs rename to crates/ret/src/low_level/deposit_rule_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/execution_summary.rs b/crates/ret/src/low_level/execution_summary/execution_summary.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/execution_summary.rs rename to crates/ret/src/low_level/execution_summary/execution_summary.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/fee_locks.rs b/crates/ret/src/low_level/execution_summary/fee_locks.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/fee_locks.rs rename to crates/ret/src/low_level/execution_summary/fee_locks.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/fee_summary.rs b/crates/ret/src/low_level/execution_summary/fee_summary.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/fee_summary.rs rename to crates/ret/src/low_level/execution_summary/fee_summary.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/mod.rs b/crates/ret/src/low_level/execution_summary/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/mod.rs rename to crates/ret/src/low_level/execution_summary/mod.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/new_entities.rs b/crates/ret/src/low_level/execution_summary/new_entities.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/new_entities.rs rename to crates/ret/src/low_level/execution_summary/new_entities.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/newly_created_resource.rs b/crates/ret/src/low_level/execution_summary/newly_created_resource.rs similarity index 96% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/newly_created_resource.rs rename to crates/ret/src/low_level/execution_summary/newly_created_resource.rs index 52eb78f3b..f041fa348 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/newly_created_resource.rs +++ b/crates/ret/src/low_level/execution_summary/newly_created_resource.rs @@ -103,7 +103,7 @@ impl NewlyCreatedResource { #[allow(unused)] impl NewlyCreatedResource { - pub(crate) fn sample_mainnet_xrd() -> Self { + pub fn sample_mainnet_xrd() -> Self { Self::with( "Rad", "XRD", @@ -113,7 +113,7 @@ impl NewlyCreatedResource { ) } - pub(crate) fn sample_mainnet_candy() -> Self { + pub fn sample_mainnet_candy() -> Self { Self::with( "Candy", "CANDY", @@ -123,7 +123,7 @@ impl NewlyCreatedResource { ) } - pub(crate) fn sample_stokenet_gc() -> Self { + pub fn sample_stokenet_gc() -> Self { Self::with( "GC Tokens (GC)", "GC", @@ -133,7 +133,7 @@ impl NewlyCreatedResource { ) } - pub(crate) fn sample_stokenet_gum() -> Self { + pub fn sample_stokenet_gum() -> Self { Self::with( "GC Gumballs (GUM)", "GUM", diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/reserved_instruction.rs b/crates/ret/src/low_level/execution_summary/reserved_instruction.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/reserved_instruction.rs rename to crates/ret/src/low_level/execution_summary/reserved_instruction.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/fungible_resource_indicator.rs b/crates/ret/src/low_level/execution_summary/resource_indicator/fungible_resource_indicator.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/fungible_resource_indicator.rs rename to crates/ret/src/low_level/execution_summary/resource_indicator/fungible_resource_indicator.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/fungible_resource_indicator_uniffi_fn.rs b/crates/ret/src/low_level/execution_summary/resource_indicator/fungible_resource_indicator_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/fungible_resource_indicator_uniffi_fn.rs rename to crates/ret/src/low_level/execution_summary/resource_indicator/fungible_resource_indicator_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/mod.rs b/crates/ret/src/low_level/execution_summary/resource_indicator/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/mod.rs rename to crates/ret/src/low_level/execution_summary/resource_indicator/mod.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/non_fungible_resource_indicator.rs b/crates/ret/src/low_level/execution_summary/resource_indicator/non_fungible_resource_indicator.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/non_fungible_resource_indicator.rs rename to crates/ret/src/low_level/execution_summary/resource_indicator/non_fungible_resource_indicator.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/non_fungible_resource_indicator_uniffi_fn.rs b/crates/ret/src/low_level/execution_summary/resource_indicator/non_fungible_resource_indicator_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/non_fungible_resource_indicator_uniffi_fn.rs rename to crates/ret/src/low_level/execution_summary/resource_indicator/non_fungible_resource_indicator_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/predicted.rs b/crates/ret/src/low_level/execution_summary/resource_indicator/predicted.rs similarity index 98% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/predicted.rs rename to crates/ret/src/low_level/execution_summary/resource_indicator/predicted.rs index fce026297..8af85102e 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/predicted.rs +++ b/crates/ret/src/low_level/execution_summary/resource_indicator/predicted.rs @@ -1,4 +1,4 @@ -use radix_rust::prelude::{IndexMap, IndexSet}; +use radix_rust::prelude::IndexSet; use crate::prelude::*; diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/resource_indicator.rs b/crates/ret/src/low_level/execution_summary/resource_indicator/resource_indicator.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/resource_indicator.rs rename to crates/ret/src/low_level/execution_summary/resource_indicator/resource_indicator.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/resource_indicator_uniffi_fn.rs b/crates/ret/src/low_level/execution_summary/resource_indicator/resource_indicator_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/execution_summary/resource_indicator/resource_indicator_uniffi_fn.rs rename to crates/ret/src/low_level/execution_summary/resource_indicator/resource_indicator_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/intent_signature.rs b/crates/ret/src/low_level/intent_signature.rs similarity index 98% rename from src/wrapped_radix_engine_toolkit/low_level/intent_signature.rs rename to crates/ret/src/low_level/intent_signature.rs index 89bdb0a52..37b4f38a8 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/intent_signature.rs +++ b/crates/ret/src/low_level/intent_signature.rs @@ -4,7 +4,7 @@ use crate::prelude::*; Clone, Copy, PartialOrd, Ord, Debug, PartialEq, Eq, Hash, uniffi::Record, )] pub struct IntentSignature { - pub(crate) secret_magic: SignatureWithPublicKey, + pub secret_magic: SignatureWithPublicKey, } impl IntentSignature { diff --git a/src/wrapped_radix_engine_toolkit/low_level/intent_signature_uniffi_fn.rs b/crates/ret/src/low_level/intent_signature_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/intent_signature_uniffi_fn.rs rename to crates/ret/src/low_level/intent_signature_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/intent_signatures.rs b/crates/ret/src/low_level/intent_signatures.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/intent_signatures.rs rename to crates/ret/src/low_level/intent_signatures.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/manifest_summary.rs b/crates/ret/src/low_level/manifest_summary.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/manifest_summary.rs rename to crates/ret/src/low_level/manifest_summary.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/message/message.rs b/crates/ret/src/low_level/message/message.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/message/message.rs rename to crates/ret/src/low_level/message/message.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/message/message_uniffi_fn.rs b/crates/ret/src/low_level/message/message_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/message/message_uniffi_fn.rs rename to crates/ret/src/low_level/message/message_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/message/mod.rs b/crates/ret/src/low_level/message/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/message/mod.rs rename to crates/ret/src/low_level/message/mod.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/message/plaintext_message/mod.rs b/crates/ret/src/low_level/message/plaintext_message/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/message/plaintext_message/mod.rs rename to crates/ret/src/low_level/message/plaintext_message/mod.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/message/plaintext_message/plaintext_message.rs b/crates/ret/src/low_level/message/plaintext_message/plaintext_message.rs similarity index 96% rename from src/wrapped_radix_engine_toolkit/low_level/message/plaintext_message/plaintext_message.rs rename to crates/ret/src/low_level/message/plaintext_message/plaintext_message.rs index 6e2834a68..65d6d2c7a 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/message/plaintext_message/plaintext_message.rs +++ b/crates/ret/src/low_level/message/plaintext_message/plaintext_message.rs @@ -53,7 +53,7 @@ impl HasSampleValues for PlaintextMessage { impl PlaintextMessage { #[allow(unused)] - pub(crate) fn sample_binary() -> Self { + pub fn sample_binary() -> Self { Self { mime_type: "".to_owned(), message: MessageContents::BinaryMessage { @@ -67,7 +67,6 @@ impl PlaintextMessage { mod tests { use super::*; - use crate::prelude::*; #[allow(clippy::upper_case_acronyms)] type SUT = PlaintextMessage; diff --git a/src/wrapped_radix_engine_toolkit/low_level/message/plaintext_message/plaintext_message_contents.rs b/crates/ret/src/low_level/message/plaintext_message/plaintext_message_contents.rs similarity index 98% rename from src/wrapped_radix_engine_toolkit/low_level/message/plaintext_message/plaintext_message_contents.rs rename to crates/ret/src/low_level/message/plaintext_message/plaintext_message_contents.rs index 003d57842..5fd40bfac 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/message/plaintext_message/plaintext_message_contents.rs +++ b/crates/ret/src/low_level/message/plaintext_message/plaintext_message_contents.rs @@ -64,7 +64,6 @@ impl HasSampleValues for MessageContents { mod tests { use super::*; - use crate::prelude::*; #[allow(clippy::upper_case_acronyms)] type SUT = MessageContents; diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/metadata.rs b/crates/ret/src/low_level/metadata.rs similarity index 99% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/metadata.rs rename to crates/ret/src/low_level/metadata.rs index 77228646d..d5c7dcf5f 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/metadata.rs +++ b/crates/ret/src/low_level/metadata.rs @@ -1,5 +1,4 @@ use crate::prelude::*; -use strum::*; #[derive(Debug, PartialEq, Eq, strum::EnumString, strum::Display)] #[strum(serialize_all = "snake_case")] diff --git a/src/wrapped_radix_engine_toolkit/low_level/mod.rs b/crates/ret/src/low_level/mod.rs similarity index 80% rename from src/wrapped_radix_engine_toolkit/low_level/mod.rs rename to crates/ret/src/low_level/mod.rs index a11f9a562..e8ee59ce6 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/mod.rs +++ b/crates/ret/src/low_level/mod.rs @@ -1,18 +1,24 @@ mod address_conversion; +mod addresses_manifest_builder_support; +mod assert_manifest; mod compiled_notarized_intent; mod compiled_notarized_intent_uniffi_fn; +mod deposit_rule; mod execution_summary; mod intent_signature; mod intent_signature_uniffi_fn; mod intent_signatures; mod manifest_summary; mod message; +mod metadata; mod notarized_transaction; mod notarized_transaction_uniffi_fn; mod notary_signature; mod notary_signature_uniffi_fn; mod public_key_hash; mod public_key_hash_uniffi_fn; +mod resource_or_non_fungible; +mod resource_or_non_fungible_uniffi_fn; mod sbor_depth_validation; mod signed_intent; mod signed_intent_uniffi_fn; @@ -25,22 +31,27 @@ mod transaction_intent_uniffi_fn; mod transaction_manifest; mod transaction_receipt; -pub use address_conversion::*; +pub use addresses_manifest_builder_support::*; +pub use assert_manifest::*; pub use compiled_notarized_intent::*; pub use compiled_notarized_intent_uniffi_fn::*; +pub use deposit_rule::*; pub use execution_summary::*; pub use intent_signature::*; pub use intent_signature_uniffi_fn::*; pub use intent_signatures::*; pub use manifest_summary::*; pub use message::*; +pub use metadata::*; pub use notarized_transaction::*; pub use notarized_transaction_uniffi_fn::*; pub use notary_signature::*; pub use notary_signature_uniffi_fn::*; pub use public_key_hash::*; pub use public_key_hash_uniffi_fn::*; -pub(crate) use sbor_depth_validation::*; +pub use resource_or_non_fungible::*; +pub use resource_or_non_fungible_uniffi_fn::*; + pub use signed_intent::*; pub use signed_intent_uniffi_fn::*; pub use transaction_classes::*; diff --git a/src/wrapped_radix_engine_toolkit/low_level/notarized_transaction.rs b/crates/ret/src/low_level/notarized_transaction.rs similarity index 98% rename from src/wrapped_radix_engine_toolkit/low_level/notarized_transaction.rs rename to crates/ret/src/low_level/notarized_transaction.rs index 088132215..78a1188a2 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/notarized_transaction.rs +++ b/crates/ret/src/low_level/notarized_transaction.rs @@ -73,7 +73,7 @@ impl NotarizedTransaction { /// Utility function which uses `NotarizedTransaction::new(, )` /// and SHOULD return `Err` if `depth > NotarizedTransaction::MAX_SBOR_DEPTH`, which /// we can assert in unit tests. - pub(crate) fn test_with_sbor_depth( + pub fn test_with_sbor_depth( depth: usize, network_id: NetworkID, ) -> Result { @@ -88,7 +88,7 @@ impl NotarizedTransaction { ) } - pub(crate) const MAX_SBOR_DEPTH: usize = SignedIntent::MAX_SBOR_DEPTH - 1; + pub const MAX_SBOR_DEPTH: usize = SignedIntent::MAX_SBOR_DEPTH - 1; } impl HasSampleValues for NotarizedTransaction { diff --git a/src/wrapped_radix_engine_toolkit/low_level/notarized_transaction_uniffi_fn.rs b/crates/ret/src/low_level/notarized_transaction_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/notarized_transaction_uniffi_fn.rs rename to crates/ret/src/low_level/notarized_transaction_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/notary_signature.rs b/crates/ret/src/low_level/notary_signature.rs similarity index 98% rename from src/wrapped_radix_engine_toolkit/low_level/notary_signature.rs rename to crates/ret/src/low_level/notary_signature.rs index f7c99ef67..7061493a5 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/notary_signature.rs +++ b/crates/ret/src/low_level/notary_signature.rs @@ -14,7 +14,7 @@ use crate::prelude::*; uniffi::Record, )] pub struct NotarySignature { - pub(crate) secret_magic: Signature, + pub secret_magic: Signature, } impl From for NotarySignature { diff --git a/src/wrapped_radix_engine_toolkit/low_level/notary_signature_uniffi_fn.rs b/crates/ret/src/low_level/notary_signature_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/notary_signature_uniffi_fn.rs rename to crates/ret/src/low_level/notary_signature_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/public_key_hash.rs b/crates/ret/src/low_level/public_key_hash.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/public_key_hash.rs rename to crates/ret/src/low_level/public_key_hash.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/public_key_hash_uniffi_fn.rs b/crates/ret/src/low_level/public_key_hash_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/public_key_hash_uniffi_fn.rs rename to crates/ret/src/low_level/public_key_hash_uniffi_fn.rs diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/resource_or_non_fungible.rs b/crates/ret/src/low_level/resource_or_non_fungible.rs similarity index 83% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/resource_or_non_fungible.rs rename to crates/ret/src/low_level/resource_or_non_fungible.rs index d71711939..2d734a252 100644 --- a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/resource_or_non_fungible.rs +++ b/crates/ret/src/low_level/resource_or_non_fungible.rs @@ -1,5 +1,10 @@ use crate::prelude::*; +use radix_engine_interface::blueprints::account::{ + AccountAddAuthorizedDepositorInput as ScryptoAccountAddAuthorizedDepositorInput, + AccountRemoveResourcePreferenceInput as ScryptoAccountRemoveResourcePreferenceInput, +}; + /// The addresses that can be added as exception to the `DepositRule` #[derive( Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, uniffi::Enum, @@ -13,6 +18,28 @@ pub enum ResourceOrNonFungible { NonFungible { value: NonFungibleGlobalId }, } +impl From for ScryptoAccountAddAuthorizedDepositorInput { + fn from(value: ResourceOrNonFungible) -> Self { + ScryptoAccountAddAuthorizedDepositorInput { + badge: value.into(), + } + } +} +impl From + for ScryptoAccountRemoveResourcePreferenceInput +{ + fn from(value: ResourceOrNonFungible) -> Self { + match value { + ResourceOrNonFungible::Resource { value } => Self { + resource_address: value.into(), + }, + ResourceOrNonFungible::NonFungible { value } => Self { + resource_address: value.resource_address.into(), + }, + } + } +} + impl std::fmt::Display for ResourceOrNonFungible { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/resource_or_non_fungible_uniffi_fn.rs b/crates/ret/src/low_level/resource_or_non_fungible_uniffi_fn.rs similarity index 100% rename from src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/resource_or_non_fungible_uniffi_fn.rs rename to crates/ret/src/low_level/resource_or_non_fungible_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/sbor_depth_validation.rs b/crates/ret/src/low_level/sbor_depth_validation.rs similarity index 89% rename from src/wrapped_radix_engine_toolkit/low_level/sbor_depth_validation.rs rename to crates/ret/src/low_level/sbor_depth_validation.rs index ffc60a4be..8c671a295 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/sbor_depth_validation.rs +++ b/crates/ret/src/low_level/sbor_depth_validation.rs @@ -1,10 +1,12 @@ +#[allow(unused)] use crate::prelude::*; - +#[allow(unused)] use radix_common::prelude::{ manifest_encode as Scrypto_manifest_encode, ScryptoValue as ScryptoScryptoValue, MANIFEST_SBOR_V1_MAX_DEPTH, SCRYPTO_SBOR_V1_MAX_DEPTH, }; +#[allow(unused)] use sbor::{ CustomValue as ScryptoCustomValue, CustomValueKind as ScryptoCustomValueKind, Value as ScryptoValue, @@ -26,16 +28,12 @@ where } #[cfg(test)] -pub(crate) fn scrypto_value_with_sbor_depth( - depth: usize, -) -> ScryptoScryptoValue { +pub fn scrypto_value_with_sbor_depth(depth: usize) -> ScryptoScryptoValue { sbor_value_with_depth(depth) } #[cfg(test)] -pub(crate) fn manifest_value_with_sbor_depth( - depth: usize, -) -> ScryptoManifestValue { +pub fn manifest_value_with_sbor_depth(depth: usize) -> ScryptoManifestValue { sbor_value_with_depth(depth) } diff --git a/src/wrapped_radix_engine_toolkit/low_level/signed_intent.rs b/crates/ret/src/low_level/signed_intent.rs similarity index 97% rename from src/wrapped_radix_engine_toolkit/low_level/signed_intent.rs rename to crates/ret/src/low_level/signed_intent.rs index 9360a115f..60843030e 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/signed_intent.rs +++ b/crates/ret/src/low_level/signed_intent.rs @@ -138,7 +138,7 @@ impl SignedIntent { /// Utility function which uses `SignedIntent::new(, )` /// and SHOULD return `Err` if `depth > SignedIntent::MAX_SBOR_DEPTH`, which /// we can assert in unit tests. - pub(crate) fn test_with_sbor_depth( + pub fn test_with_sbor_depth( depth: usize, network_id: NetworkID, ) -> Result { @@ -161,15 +161,13 @@ impl SignedIntent { ) } - pub(crate) const MAX_SBOR_DEPTH: usize = - TransactionIntent::MAX_SBOR_DEPTH - 1; + pub const MAX_SBOR_DEPTH: usize = TransactionIntent::MAX_SBOR_DEPTH - 1; } #[cfg(test)] mod tests { use super::*; - use crate::prelude::*; #[allow(clippy::upper_case_acronyms)] type SUT = SignedIntent; @@ -290,7 +288,7 @@ mod tests { let res = compile_signed_intent(invalid_signed_intent()); assert_eq!( res, - Err(CommonError::InvalidSignedIntentFailedToEncode { underlying: "MismatchingArrayElementValueKind { element_value_kind: 7, actual_value_kind: 8 }".to_owned() }) + Err(CommonError::InvalidSignedIntentFailedToEncode { underlying: "MismatchingArrayElementValueKind { element_value_kind: 7, actual_value_kind: 8 }".to_owned() }) ); } } diff --git a/src/wrapped_radix_engine_toolkit/low_level/signed_intent_uniffi_fn.rs b/crates/ret/src/low_level/signed_intent_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/signed_intent_uniffi_fn.rs rename to crates/ret/src/low_level/signed_intent_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_classes/detailed_manifest_class.rs b/crates/ret/src/low_level/transaction_classes/detailed_manifest_class.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_classes/detailed_manifest_class.rs rename to crates/ret/src/low_level/transaction_classes/detailed_manifest_class.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_classes/mod.rs b/crates/ret/src/low_level/transaction_classes/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_classes/mod.rs rename to crates/ret/src/low_level/transaction_classes/mod.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/mod.rs b/crates/ret/src/low_level/transaction_classes/types/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/mod.rs rename to crates/ret/src/low_level/transaction_classes/types/mod.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/resource_preference_update.rs b/crates/ret/src/low_level/transaction_classes/types/resource_preference_update.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/resource_preference_update.rs rename to crates/ret/src/low_level/transaction_classes/types/resource_preference_update.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/tracked_pool_contribution.rs b/crates/ret/src/low_level/transaction_classes/types/tracked_pool_contribution.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/tracked_pool_contribution.rs rename to crates/ret/src/low_level/transaction_classes/types/tracked_pool_contribution.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/tracked_pool_redemption.rs b/crates/ret/src/low_level/transaction_classes/types/tracked_pool_redemption.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/tracked_pool_redemption.rs rename to crates/ret/src/low_level/transaction_classes/types/tracked_pool_redemption.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/tracked_validator_claim.rs b/crates/ret/src/low_level/transaction_classes/types/tracked_validator_claim.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/tracked_validator_claim.rs rename to crates/ret/src/low_level/transaction_classes/types/tracked_validator_claim.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/tracked_validator_stake.rs b/crates/ret/src/low_level/transaction_classes/types/tracked_validator_stake.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/tracked_validator_stake.rs rename to crates/ret/src/low_level/transaction_classes/types/tracked_validator_stake.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/unstake_data.rs b/crates/ret/src/low_level/transaction_classes/types/unstake_data.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_classes/types/unstake_data.rs rename to crates/ret/src/low_level/transaction_classes/types/unstake_data.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/intent_hash.rs b/crates/ret/src/low_level/transaction_hashes/intent_hash.rs similarity index 98% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/intent_hash.rs rename to crates/ret/src/low_level/transaction_hashes/intent_hash.rs index 758463841..c560c2fcb 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/intent_hash.rs +++ b/crates/ret/src/low_level/transaction_hashes/intent_hash.rs @@ -15,7 +15,6 @@ impl HasSampleValues for IntentHash { mod tests { use super::*; - use crate::prelude::*; #[allow(clippy::upper_case_acronyms)] type SUT = IntentHash; diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/intent_hash_uniffi_fn.rs b/crates/ret/src/low_level/transaction_hashes/intent_hash_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/intent_hash_uniffi_fn.rs rename to crates/ret/src/low_level/transaction_hashes/intent_hash_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/mod.rs b/crates/ret/src/low_level/transaction_hashes/mod.rs similarity index 82% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/mod.rs rename to crates/ret/src/low_level/transaction_hashes/mod.rs index cea330309..a8dce3b12 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/mod.rs +++ b/crates/ret/src/low_level/transaction_hashes/mod.rs @@ -5,8 +5,8 @@ mod signed_intent_hash_uniffi_fn; mod transaction_hashes; mod validate_and_decode_hash; -pub use intent_hash::*; pub use intent_hash_uniffi_fn::*; -pub use signed_intent_hash::*; + pub use signed_intent_hash_uniffi_fn::*; pub use transaction_hashes::*; +pub use validate_and_decode_hash::*; diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/signed_intent_hash.rs b/crates/ret/src/low_level/transaction_hashes/signed_intent_hash.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/signed_intent_hash.rs rename to crates/ret/src/low_level/transaction_hashes/signed_intent_hash.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/signed_intent_hash_uniffi_fn.rs b/crates/ret/src/low_level/transaction_hashes/signed_intent_hash_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/signed_intent_hash_uniffi_fn.rs rename to crates/ret/src/low_level/transaction_hashes/signed_intent_hash_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/transaction_hashes.rs b/crates/ret/src/low_level/transaction_hashes/transaction_hashes.rs similarity index 97% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/transaction_hashes.rs rename to crates/ret/src/low_level/transaction_hashes/transaction_hashes.rs index 90fc95aad..4beca0968 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/transaction_hashes.rs +++ b/crates/ret/src/low_level/transaction_hashes/transaction_hashes.rs @@ -1,7 +1,5 @@ use crate::prelude::*; -use crate::wrapped_radix_engine_toolkit::low_level::transaction_hashes::validate_and_decode_hash::validate_and_decode_hash; - /// This macro exists since UniFFI does not support generics currently, when/if /// UniFFI does, we SHOULD remove this macro and use generics. macro_rules! decl_tx_hash { @@ -68,7 +66,7 @@ macro_rules! decl_tx_hash { } impl $struct_name { - pub(crate) fn from_scrypto( + pub fn from_scrypto( scrypto: $scrypto_struct_name, network_id: NetworkID, ) -> Self { diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/validate_and_decode_hash.rs b/crates/ret/src/low_level/transaction_hashes/validate_and_decode_hash.rs similarity index 88% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/validate_and_decode_hash.rs rename to crates/ret/src/low_level/transaction_hashes/validate_and_decode_hash.rs index b6be9012b..76ca6e81c 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_hashes/validate_and_decode_hash.rs +++ b/crates/ret/src/low_level/transaction_hashes/validate_and_decode_hash.rs @@ -1,15 +1,15 @@ use crate::prelude::*; -fn validate_and_decode_hash_try_network( +pub fn validate_and_decode_hash_try_network( bech32_encoded_hash: &str, network_id: NetworkID, -) -> Result { +) -> Result { ScryptoTransactionHashBech32Decoder::new(&network_id.network_definition()) .validate_and_decode::(bech32_encoded_hash) - .map_err(|_| ()) + .map_err(|_| CommonError::Unknown) } -pub(crate) fn validate_and_decode_hash( +pub fn validate_and_decode_hash( bech32_encoded_hash: &str, ) -> Result<(T, NetworkID)> { if let Some(t) = enum_iterator::all::() diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_header.rs b/crates/ret/src/low_level/transaction_header.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_header.rs rename to crates/ret/src/low_level/transaction_header.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_header_uniffi_fn.rs b/crates/ret/src/low_level/transaction_header_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_header_uniffi_fn.rs rename to crates/ret/src/low_level/transaction_header_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_intent.rs b/crates/ret/src/low_level/transaction_intent.rs similarity index 98% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_intent.rs rename to crates/ret/src/low_level/transaction_intent.rs index c1e38a702..7d1749d63 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_intent.rs +++ b/crates/ret/src/low_level/transaction_intent.rs @@ -135,7 +135,7 @@ impl TransactionIntent { /// Utility function which uses `TransactionIntent::new(, , )` /// and SHOULD return `Err` if `depth > TransactionIntent::MAX_SBOR_DEPTH`, which /// we can assert in unit tests. - pub(crate) fn test_with_sbor_depth( + pub fn test_with_sbor_depth( depth: usize, network_id: NetworkID, ) -> Result { @@ -156,7 +156,7 @@ impl TransactionIntent { }) } - pub(crate) const MAX_SBOR_DEPTH: usize = Instructions::MAX_SBOR_DEPTH; + pub const MAX_SBOR_DEPTH: usize = Instructions::MAX_SBOR_DEPTH; } #[cfg(test)] diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_intent_uniffi_fn.rs b/crates/ret/src/low_level/transaction_intent_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_intent_uniffi_fn.rs rename to crates/ret/src/low_level/transaction_intent_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/blobs/blob.rs b/crates/ret/src/low_level/transaction_manifest/blobs/blob.rs similarity index 98% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/blobs/blob.rs rename to crates/ret/src/low_level/transaction_manifest/blobs/blob.rs index 069c7bd84..af321dae8 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/blobs/blob.rs +++ b/crates/ret/src/low_level/transaction_manifest/blobs/blob.rs @@ -3,7 +3,7 @@ use crate::prelude::*; /// Blob is a wrapper a bag of bytes #[derive(Clone, PartialEq, Eq, Debug, derive_more::Display, uniffi::Record)] pub struct Blob { - pub(crate) secret_magic: BagOfBytes, + pub secret_magic: BagOfBytes, } impl Blob { diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/blobs/blobs.rs b/crates/ret/src/low_level/transaction_manifest/blobs/blobs.rs similarity index 98% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/blobs/blobs.rs rename to crates/ret/src/low_level/transaction_manifest/blobs/blobs.rs index 7568411a0..7560806ab 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/blobs/blobs.rs +++ b/crates/ret/src/low_level/transaction_manifest/blobs/blobs.rs @@ -3,7 +3,7 @@ use crate::prelude::*; /// Vec of Blobs #[derive(Clone, PartialEq, Eq, Debug, uniffi::Record)] pub struct Blobs { - pub(crate) secret_magic: BlobsSecretMagic, + pub secret_magic: BlobsSecretMagic, } impl From for Blobs { diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/blobs/blobs_secret_magic.rs b/crates/ret/src/low_level/transaction_manifest/blobs/blobs_secret_magic.rs similarity index 94% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/blobs/blobs_secret_magic.rs rename to crates/ret/src/low_level/transaction_manifest/blobs/blobs_secret_magic.rs index 6eca2f1a6..81b216ab9 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/blobs/blobs_secret_magic.rs +++ b/crates/ret/src/low_level/transaction_manifest/blobs/blobs_secret_magic.rs @@ -3,7 +3,7 @@ use crate::prelude::*; /// Vec of Blobs #[derive(Clone, PartialEq, Eq, Debug, uniffi::Record)] pub struct BlobsSecretMagic { - pub(crate) secret_magic: Vec, + pub secret_magic: Vec, } impl BlobsSecretMagic { @@ -20,7 +20,7 @@ impl BlobsSecretMagic { } } - pub(crate) fn from_bags(bags: I) -> Self + pub fn from_bags(bags: I) -> Self where I: IntoIterator, { @@ -40,7 +40,7 @@ impl From for BlobsSecretMagic { } } -pub(crate) type ScryptoBlobsMap = IndexMap>; +pub type ScryptoBlobsMap = IndexMap>; impl From for BlobsSecretMagic { fn from(value: ScryptoBlobsMap) -> Self { diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/blobs/mod.rs b/crates/ret/src/low_level/transaction_manifest/blobs/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/blobs/mod.rs rename to crates/ret/src/low_level/transaction_manifest/blobs/mod.rs diff --git a/crates/ret/src/low_level/transaction_manifest/execution_summary/mod.rs b/crates/ret/src/low_level/transaction_manifest/execution_summary/mod.rs new file mode 100644 index 000000000..119c9ef3c --- /dev/null +++ b/crates/ret/src/low_level/transaction_manifest/execution_summary/mod.rs @@ -0,0 +1 @@ +mod transaction_manifest_execution_summary; diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/execution_summary/transaction_manifest_execution_summary.rs b/crates/ret/src/low_level/transaction_manifest/execution_summary/transaction_manifest_execution_summary.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/execution_summary/transaction_manifest_execution_summary.rs rename to crates/ret/src/low_level/transaction_manifest/execution_summary/transaction_manifest_execution_summary.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/instructions/instructions.rs b/crates/ret/src/low_level/transaction_manifest/instructions/instructions.rs similarity index 93% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/instructions/instructions.rs rename to crates/ret/src/low_level/transaction_manifest/instructions/instructions.rs index 3d521e629..1b94dc9f5 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/instructions/instructions.rs +++ b/crates/ret/src/low_level/transaction_manifest/instructions/instructions.rs @@ -1,6 +1,5 @@ use crate::prelude::*; -use radix_common::prelude::MANIFEST_SBOR_V1_MAX_DEPTH; use radix_engine_toolkit::functions::address::decode as RET_decode_address; #[derive(Clone, Debug, PartialEq, Eq, derive_more::Display, uniffi::Record)] @@ -21,7 +20,7 @@ impl Deref for Instructions { #[cfg(test)] impl Instructions { /// For tests only, does not validate the SBOR depth of the instructions. - pub(crate) fn new_unchecked( + pub fn new_unchecked( instructions: Vec, network_id: NetworkID, ) -> Self { @@ -33,7 +32,7 @@ impl Instructions { } impl Instructions { - pub(crate) fn instructions(&self) -> &Vec { + pub fn instructions(&self) -> &Vec { self.deref() } } @@ -99,10 +98,11 @@ impl Instructions { /// Utility function which uses `Instructions::new(, )` /// and SHOULD return `Err` if `depth > Instructions::MAX_SBOR_DEPTH`, which /// we can assert in unit tests. - pub(crate) fn test_with_sbor_depth( + pub fn test_with_sbor_depth( depth: usize, network_id: NetworkID, ) -> Result { + use crate::low_level::sbor_depth_validation::manifest_value_with_sbor_depth; let nested_value = manifest_value_with_sbor_depth(depth); let dummy_address = ComponentAddress::with_node_id_bytes(&[0xffu8; 29], network_id); @@ -119,7 +119,8 @@ impl Instructions { .and_then(|x: String| Self::new(x, network_id)) } - pub(crate) const MAX_SBOR_DEPTH: usize = MANIFEST_SBOR_V1_MAX_DEPTH - 3; + pub const MAX_SBOR_DEPTH: usize = + radix_common::prelude::MANIFEST_SBOR_V1_MAX_DEPTH - 3; } fn extract_error_from_addr( @@ -136,8 +137,8 @@ fn extract_error_from_addr( }; if network_id != expected_network { CommonError::InvalidInstructionsWrongNetwork { - found_in_instructions: network_id, - specified_to_instructions_ctor: expected_network, + found_in_instructions: network_id.discriminant(), + specified_to_instructions_ctor: expected_network.discriminant(), } } else { CommonError::InvalidInstructionsString { @@ -153,7 +154,7 @@ fn extract_error_from_error( ) -> CommonError { use radix_transactions::manifest::parser::ParserError; use radix_transactions::manifest::parser::ParserErrorKind::*; - use GeneratorError; + use GeneratorErrorKind::*; let n = expected_network; match err { @@ -191,7 +192,7 @@ impl HasSampleValues for Instructions { } impl Instructions { - pub(crate) fn empty(network_id: NetworkID) -> Self { + pub fn empty(network_id: NetworkID) -> Self { Self { secret_magic: InstructionsSecretMagic::new(Vec::new()), network_id, @@ -200,7 +201,7 @@ impl Instructions { } impl Instructions { - pub(crate) fn sample_mainnet_instructions_string() -> String { + pub fn sample_mainnet_instructions_string() -> String { include_str!(concat!(env!("FIXTURES_TX"), "resource_transfer.rtm")) .to_owned() } @@ -241,7 +242,7 @@ impl Instructions { // https://github.com/radixdlt/radix-engine-toolkit/blob/cf2f4b4d6de56233872e11959861fbf12db8ddf6/crates/radix-engine-toolkit/tests/manifests/account/multi_account_resource_transfer.rtm // but modified, changed `None` -> `Enum<0u8>()`, also changed `"account_a_bucket"` -> `"bucket1"`, `"account_b_bucket"` -> `"bucket2"`, etc. - pub(crate) fn sample_other_simulator_instructions_string() -> String { + pub fn sample_other_simulator_instructions_string() -> String { include_str!(concat!( env!("FIXTURES_TX"), "multi_account_resource_transfer.rtm" @@ -300,8 +301,9 @@ mod tests { NetworkID::Stokenet ), Err(CommonError::InvalidInstructionsWrongNetwork { - found_in_instructions: NetworkID::Mainnet, + found_in_instructions: NetworkID::Mainnet.discriminant(), specified_to_instructions_ctor: NetworkID::Stokenet + .discriminant() }) ); } @@ -420,8 +422,9 @@ mod tests { NetworkID::Simulator ), CommonError::InvalidInstructionsWrongNetwork { - found_in_instructions: NetworkID::Mainnet, + found_in_instructions: NetworkID::Mainnet.discriminant(), specified_to_instructions_ctor: NetworkID::Simulator + .discriminant() } ); } @@ -450,8 +453,9 @@ mod tests { NetworkID::Simulator ), CommonError::InvalidInstructionsWrongNetwork { - found_in_instructions: NetworkID::Mainnet, + found_in_instructions: NetworkID::Mainnet.discriminant(), specified_to_instructions_ctor: NetworkID::Simulator + .discriminant() } ); } diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/instructions/instructions_secret_magic.rs b/crates/ret/src/low_level/transaction_manifest/instructions/instructions_secret_magic.rs similarity index 60% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/instructions/instructions_secret_magic.rs rename to crates/ret/src/low_level/transaction_manifest/instructions/instructions_secret_magic.rs index 723298cfe..8f00f03c6 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/instructions/instructions_secret_magic.rs +++ b/crates/ret/src/low_level/transaction_manifest/instructions/instructions_secret_magic.rs @@ -10,20 +10,22 @@ use crate::prelude::*; pub struct InstructionsSecretMagic(pub Vec); impl InstructionsSecretMagic { - pub(crate) fn instructions(&self) -> &Vec { + pub fn instructions(&self) -> &Vec { &self.0 } - pub(crate) fn new(instructions: Vec) -> Self { + pub fn new(instructions: Vec) -> Self { Self(instructions) } } -uniffi::custom_type!(InstructionsSecretMagic, BagOfBytes); - -impl crate::UniffiCustomTypeConverter for InstructionsSecretMagic { - type Builtin = BagOfBytes; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(InstructionsSecretMagic, BagOfBytes, { + remote, + from_custom: |secret_magic| { + RET_compile_instructions(&secret_magic.0) + .map(|b| b.into()) + .expect("to never fail") + }, + try_into_custom: |val| { let bytes: &[u8] = val.bytes(); RET_decompile_instructions(bytes) .map_err(|e| { @@ -34,13 +36,7 @@ impl crate::UniffiCustomTypeConverter for InstructionsSecretMagic { }) .map(Self::new) } - - fn from_custom(obj: Self) -> Self::Builtin { - RET_compile_instructions(&obj.0) - .map(|b| b.into()) - .expect("to never fail") - } -} +}); impl From for InstructionsSecretMagic { fn from(value: ScryptoInstructions) -> Self { @@ -64,7 +60,6 @@ impl HasSampleValues for InstructionsSecretMagic { #[cfg(test)] mod tests { use super::*; - use crate::prelude::*; #[allow(clippy::upper_case_acronyms)] type SUT = InstructionsSecretMagic; @@ -92,29 +87,29 @@ mod tests { assert_ne!(SUT::sample(), SUT::sample_other()); } - #[test] - fn manual_perform_uniffi_conversion_successful() { - let sut = SUT::sample(); - let builtin = BagOfBytes::from_hex("4d20220212001300").unwrap(); + // #[test] + // fn manual_perform_uniffi_conversion_successful() { + // let sut = SUT::sample(); + // let builtin = BagOfBytes::from_hex("4d20220212001300").unwrap(); - let ffi_side = - ::from_custom(sut.clone()); + // let ffi_side = + // ::from_custom(sut.clone()); - assert_eq!(ffi_side.to_hex(), builtin.to_hex()); + // assert_eq!(ffi_side.to_hex(), builtin.to_hex()); - let from_ffi_side = - ::into_custom(ffi_side) - .unwrap(); + // let from_ffi_side = + // ::into_custom(ffi_side) + // .unwrap(); - assert_eq!(sut, from_ffi_side); - } + // assert_eq!(sut, from_ffi_side); + // } - #[test] - fn manual_perform_uniffi_conversion_fail() { - let builtin = BagOfBytes::from_hex("deadbeef").unwrap(); - assert!(::into_custom( - builtin - ) - .is_err()); - } + // #[test] + // fn manual_perform_uniffi_conversion_fail() { + // let builtin = BagOfBytes::from_hex("deadbeef").unwrap(); + // assert!(::into_custom( + // builtin + // ) + // .is_err()); + // } } diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/instructions/mod.rs b/crates/ret/src/low_level/transaction_manifest/instructions/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/instructions/mod.rs rename to crates/ret/src/low_level/transaction_manifest/instructions/mod.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/mod.rs b/crates/ret/src/low_level/transaction_manifest/mod.rs similarity index 91% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/mod.rs rename to crates/ret/src/low_level/transaction_manifest/mod.rs index 5c33fd4ae..21bf99645 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/mod.rs +++ b/crates/ret/src/low_level/transaction_manifest/mod.rs @@ -6,7 +6,7 @@ mod transaction_manifest_secret_magic; mod transaction_manifest_uniffi_fn; pub use blobs::*; -pub use execution_summary::*; + pub use instructions::*; pub use transaction_manifest::*; pub use transaction_manifest_secret_magic::*; diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/transaction_manifest.rs b/crates/ret/src/low_level/transaction_manifest/transaction_manifest.rs similarity index 95% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/transaction_manifest.rs rename to crates/ret/src/low_level/transaction_manifest/transaction_manifest.rs index b6c615388..8a294a0e2 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/transaction_manifest.rs +++ b/crates/ret/src/low_level/transaction_manifest/transaction_manifest.rs @@ -36,7 +36,7 @@ impl TransactionManifest { } impl TransactionManifest { - pub(crate) fn empty(network_id: NetworkID) -> Self { + pub fn empty(network_id: NetworkID) -> Self { Self { secret_magic: TransactionManifestSecretMagic { instructions: Instructions::empty(network_id), @@ -55,7 +55,7 @@ impl From for TransactionManifest { } impl TransactionManifest { - pub(crate) fn scrypto_manifest(&self) -> ScryptoTransactionManifest { + pub fn scrypto_manifest(&self) -> ScryptoTransactionManifest { ScryptoTransactionManifest { instructions: self.instructions().clone(), blobs: self.secret_magic.blobs.clone().into(), @@ -104,11 +104,11 @@ impl TransactionManifest { } impl TransactionManifest { - pub(crate) fn instructions(&self) -> &Vec { + pub fn instructions(&self) -> &Vec { self.secret_magic.instructions() } - pub(crate) fn blobs(&self) -> &Blobs { + pub fn blobs(&self) -> &Blobs { &self.secret_magic.blobs } @@ -160,7 +160,7 @@ impl HasSampleValues for TransactionManifest { #[allow(unused)] impl TransactionManifest { - pub(crate) fn sample_mainnet_without_lock_fee() -> Self { + pub fn sample_mainnet_without_lock_fee() -> Self { TransactionManifestSecretMagic::sample_mainnet_without_lock_fee().into() } } @@ -168,8 +168,6 @@ impl TransactionManifest { #[cfg(test)] mod tests { use super::*; - use crate::prelude::*; - use std::collections::BTreeMap; impl FromStr for TransactionManifest { type Err = crate::CommonError; @@ -262,8 +260,9 @@ mod tests { assert_eq!( SUT::new(instructions_str, NetworkID::Mainnet, Blobs::default()), Err(CommonError::InvalidInstructionsWrongNetwork { - found_in_instructions: NetworkID::Simulator, + found_in_instructions: NetworkID::Simulator.discriminant(), specified_to_instructions_ctor: NetworkID::Mainnet + .discriminant() }) ); } @@ -279,8 +278,9 @@ mod tests { assert_eq!( SUT::new(instructions_str, NetworkID::Stokenet, Blobs::default()), Err(CommonError::InvalidInstructionsWrongNetwork { - found_in_instructions: NetworkID::Mainnet, + found_in_instructions: NetworkID::Mainnet.discriminant(), specified_to_instructions_ctor: NetworkID::Stokenet + .discriminant() }) ); } diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/transaction_manifest_secret_magic.rs b/crates/ret/src/low_level/transaction_manifest/transaction_manifest_secret_magic.rs similarity index 90% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/transaction_manifest_secret_magic.rs rename to crates/ret/src/low_level/transaction_manifest/transaction_manifest_secret_magic.rs index 13dc0e21a..c0c2b5084 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/transaction_manifest_secret_magic.rs +++ b/crates/ret/src/low_level/transaction_manifest/transaction_manifest_secret_magic.rs @@ -19,7 +19,7 @@ impl TransactionManifestSecretMagic { } } - pub(crate) fn instructions(&self) -> &Vec { + pub fn instructions(&self) -> &Vec { self.instructions.instructions() } } @@ -36,7 +36,7 @@ impl HasSampleValues for TransactionManifestSecretMagic { #[allow(unused)] impl TransactionManifestSecretMagic { - pub(crate) fn sample_mainnet_without_lock_fee() -> Self { + pub fn sample_mainnet_without_lock_fee() -> Self { Self::new( Instructions::sample_mainnet_without_lock_fee(), Blobs::default(), @@ -47,7 +47,6 @@ impl TransactionManifestSecretMagic { #[cfg(test)] mod tests { use super::*; - use crate::prelude::*; #[allow(clippy::upper_case_acronyms)] type SUT = TransactionManifestSecretMagic; diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/transaction_manifest_uniffi_fn.rs b/crates/ret/src/low_level/transaction_manifest/transaction_manifest_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/transaction_manifest_uniffi_fn.rs rename to crates/ret/src/low_level/transaction_manifest/transaction_manifest_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_receipt.rs b/crates/ret/src/low_level/transaction_receipt.rs similarity index 92% rename from src/wrapped_radix_engine_toolkit/low_level/transaction_receipt.rs rename to crates/ret/src/low_level/transaction_receipt.rs index 9be54deb3..1465cb207 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_receipt.rs +++ b/crates/ret/src/low_level/transaction_receipt.rs @@ -2,7 +2,7 @@ use crate::prelude::*; #[derive(Clone, Debug)] pub struct TransactionReceipt { - pub(crate) decoded: ScryptoTransactionReceipt, + pub decoded: ScryptoTransactionReceipt, } impl TryFrom for TransactionReceipt { diff --git a/crates/ret/src/ret.udl b/crates/ret/src/ret.udl new file mode 100644 index 000000000..29971627e --- /dev/null +++ b/crates/ret/src/ret.udl @@ -0,0 +1 @@ +namespace ret {}; diff --git a/crates/ret/uniffi.toml b/crates/ret/uniffi.toml new file mode 100644 index 000000000..70d21f32d --- /dev/null +++ b/crates/ret/uniffi.toml @@ -0,0 +1,8 @@ +namespace = "ret" + +[bindings.swift] +module_name = "SargonRET" +experimental_sendable_value_types = true + +[bindings.kotlin] +package_name = "com.radixdlt.sargon.ret" diff --git a/crates/sargon/Cargo.toml b/crates/sargon/Cargo.toml new file mode 100644 index 000000000..ba120e7f7 --- /dev/null +++ b/crates/sargon/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "sargon" +version = "1.1.0" +edition = "2021" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } +actix-rt = { workspace = true } +log = { workspace = true } +pretty_assertions = { workspace = true } +sargoncommon = { path = "../common" } +drivers = { path = "../drivers" } +clients = { path = "../clients" } +hd = { path = "../hierarchical_deterministic" } +profile = { path = "../profile" } +transaction = { path = "../transaction" } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } + +[lib] +crate-type = ["staticlib", "cdylib", "lib"] diff --git a/crates/sargon/build.rs b/crates/sargon/build.rs new file mode 100644 index 000000000..cc3fe9127 --- /dev/null +++ b/crates/sargon/build.rs @@ -0,0 +1,4 @@ +pub fn main() { + uniffi::generate_scaffolding("src/sargon.udl") + .expect("Should be able to build."); +} diff --git a/crates/sargon/src/bios/bios.rs b/crates/sargon/src/bios/bios.rs new file mode 100644 index 000000000..0f34aa43f --- /dev/null +++ b/crates/sargon/src/bios/bios.rs @@ -0,0 +1,21 @@ +use crate::prelude::*; + +#[derive(Debug, uniffi::Object)] +pub struct Bios { + pub drivers: Arc, +} + +impl Bios { + pub fn into_clients(bios: Arc) -> Clients { + Clients::with_drivers(bios.drivers.clone()) + } +} + +#[uniffi::export] +impl Bios { + #[uniffi::constructor] + pub fn new(drivers: Arc) -> Arc { + install_logger(drivers.logging.clone()); + Arc::new(Bios { drivers }) + } +} diff --git a/crates/sargon/src/bios/mod.rs b/crates/sargon/src/bios/mod.rs new file mode 100644 index 000000000..7e2d0dc55 --- /dev/null +++ b/crates/sargon/src/bios/mod.rs @@ -0,0 +1,3 @@ +mod bios; + +pub use bios::*; diff --git a/crates/sargon/src/lib.rs b/crates/sargon/src/lib.rs new file mode 100644 index 000000000..3507a516c --- /dev/null +++ b/crates/sargon/src/lib.rs @@ -0,0 +1,20 @@ +#![feature(async_closure)] +#![feature(let_chains)] + +mod bios; +mod sargon_os; +mod subsystems; + +pub mod prelude { + pub use crate::bios::*; + pub use crate::sargon_os::*; + pub use crate::subsystems::*; + + pub(crate) use clients::prelude::*; + + pub(crate) use transaction::prelude::*; +} + +pub use prelude::*; + +uniffi::include_scaffolding!("sargon"); diff --git a/crates/sargon/src/sargon.udl b/crates/sargon/src/sargon.udl new file mode 100644 index 000000000..62fe00d5b --- /dev/null +++ b/crates/sargon/src/sargon.udl @@ -0,0 +1 @@ +namespace sargon {}; diff --git a/crates/sargon/src/sargon_os/mod.rs b/crates/sargon/src/sargon_os/mod.rs new file mode 100644 index 000000000..7370653a2 --- /dev/null +++ b/crates/sargon/src/sargon_os/mod.rs @@ -0,0 +1,13 @@ +mod profile_holder; +mod sargon_os; +mod sargon_os_accounts; +mod sargon_os_factors; +mod sargon_os_gateway; +mod sargon_os_profile; + +pub use profile_holder::*; +pub use sargon_os::*; +pub use sargon_os_accounts::*; +pub use sargon_os_factors::*; +pub use sargon_os_gateway::*; +pub use sargon_os_profile::*; diff --git a/crates/sargon/src/sargon_os/profile_holder.rs b/crates/sargon/src/sargon_os/profile_holder.rs new file mode 100644 index 000000000..f49e9d268 --- /dev/null +++ b/crates/sargon/src/sargon_os/profile_holder.rs @@ -0,0 +1,98 @@ +use crate::prelude::*; +use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +#[derive(Debug, uniffi::Object)] +#[allow(dead_code)] +pub struct ProfileHolder { + // This is pub for testing purposes only, i.e. causing the RwLock to be poisoned. + pub profile: RwLock, +} + +impl ProfileHolder { + pub fn new(profile: Profile) -> Self { + Self { + profile: RwLock::new(profile), + } + } +} + +impl ProfileHolder { + /// Clone the profile and return it. + pub(super) fn profile(&self) -> Profile { + self.access_profile_with(|p| p.clone()) + } + + pub fn current_network_id(&self) -> NetworkID { + self.access_profile_with(|p| p.current_network_id()) + } + + pub fn current_gateway(&self) -> Gateway { + self.access_profile_with(|p| p.current_gateway().clone()) + } + + pub fn gateways(&self) -> SavedGateways { + self.access_profile_with(|p| p.app_preferences.gateways.clone()) + } + + pub fn current_network(&self) -> ProfileNetwork { + self.access_profile_with(|p| p.current_network().clone()) + } + + /// Returns the non-hidden accounts on the current network, empty if no accounts + /// on the network + pub fn accounts_on_current_network(&self) -> Accounts { + self.access_profile_with(|p| p.accounts_on_current_network()) + } + + /// Returns the non-hidden accounts on the current network as `AccountForDisplay` + pub fn accounts_for_display_on_current_network( + &self, + ) -> AccountsForDisplay { + self.access_profile_with(|p| { + p.accounts_for_display_on_current_network() + }) + } + + /// Looks up the account by account address, returns Err if the account is + /// unknown, will return a hidden account if queried for. + pub fn account_by_address( + &self, + address: AccountAddress, + ) -> Result { + self.access_profile_with(|p| p.account_by_address(address)) + } + + pub(super) fn access_profile_with(&self, access: F) -> T + where + F: Fn(RwLockReadGuard<'_, Profile>) -> T, + { + self.profile + .try_read() + .map(access) + .expect("Implementing Wallet clients should not read and write Profile from Wallet from multiple threads.") + } + + /// Sets the profile held by this ProfileHolder to `profile`. + pub(super) fn replace_profile_with(&self, profile: Profile) -> Result<()> { + let mut lock = self + .profile + .try_write() + .map_err(|_| CommonError::UnableToAcquireWriteLockForProfile)?; + + *lock = profile; + Ok(()) + } + + /// Updates the in-memory profile held by this `ProfileHolder`, you might + /// wanna also persist the change in the `SargonOS` by saving it to secure + /// storage. + pub(super) fn update_profile_with(&self, mutate: F) -> Result + where + F: Fn(RwLockWriteGuard<'_, Profile>) -> Result, + { + self.profile + .try_write() + .map_err(|_| CommonError::UnableToAcquireWriteLockForProfile) + .and_then(mutate) + } +} diff --git a/crates/sargon/src/sargon_os/sargon_os.rs b/crates/sargon/src/sargon_os/sargon_os.rs new file mode 100644 index 000000000..6653435d9 --- /dev/null +++ b/crates/sargon/src/sargon_os/sargon_os.rs @@ -0,0 +1,515 @@ +use crate::prelude::*; + +/// The Sargon "Operating System" is the root "manager" of the Sargon library +/// which holds an in-memory Profile and a collection of "clients" which are +/// created from "drivers" which the hosts (iOS/Android wallets) "installs" +/// during app launch, enabling the Sargon "Operating System" to e.g read/write +/// to secure storage and make use of the network connection of the iPhone/Android +/// phone. +#[derive(Debug, uniffi::Object)] +pub struct SargonOS { + pub profile_holder: ProfileHolder, + pub clients: Clients, +} + +#[uniffi::export] +impl SargonOS { + pub fn drivers(&self) -> Arc { + self.clients.drivers.clone() + } +} + +/// So that we do not have to go through `self.clients`, +/// but can use e.g. `self.secure_storage` directly. +impl Deref for SargonOS { + type Target = Clients; + + fn deref(&self) -> &Self::Target { + &self.clients + } +} + +#[uniffi::export] +impl SargonOS { + #[uniffi::constructor] + pub async fn boot(bios: Arc) -> Result> { + Self::boot_with_bdfs(bios, None).await + } +} + +impl SargonOS { + pub async fn boot_with_bdfs( + bios: Arc, + bdfs_mnemonic: Option, + ) -> Result> { + // let clients = Clients::new(bios); + let clients = Bios::into_clients(bios); + + let sargon_info = SargonBuildInformation::get(); + let version = sargon_info.sargon_version; + let ret_version = sargon_info.dependencies.radix_engine_toolkit; + info!("Booting SargonOS {} (RET: {})", &version, &ret_version); + let host_info = clients.host.summary().await; + info!("Host: {}", host_info); + + let secure_storage = &clients.secure_storage; + + if let Some(loaded) = secure_storage.load_active_profile().await? { + info!("Loaded saved profile {}", &loaded.header); + let is_owner = Self::check_is_allowed_to_update_provided_profile( + &clients, &loaded, false, + ) + .await?; + + if !is_owner { + warn!("Loaded saved profile was last used on another device, will continue booting OS, but will unable to update Profile."); + } + + Ok(Arc::new(Self { + clients, + profile_holder: ProfileHolder::new(loaded), + })) + } else { + info!("No saved profile found, creating a new one..."); + let (profile, bdfs) = + Self::create_new_profile_with_bdfs(&clients, bdfs_mnemonic) + .await?; + + secure_storage.save_private_hd_factor_source(&bdfs).await?; + + secure_storage + .save_profile_and_active_profile_id(&profile) + .await?; + + info!("Saved new Profile and BDFS, finish booting SargonOS"); + + let os = Arc::new(Self { + clients, + profile_holder: ProfileHolder::new(profile), + }); + os.event_bus + .emit(EventNotification::new(Event::Booted)) + .await; + Ok(os) + } + } +} + +impl SargonOS { + pub async fn new_profile_and_bdfs( + &self, + ) -> Result<(Profile, PrivateHierarchicalDeterministicFactorSource)> { + Self::create_new_profile_and_bdfs(&self.clients).await + } + + async fn create_new_profile_and_bdfs( + clients: &Clients, + ) -> Result<(Profile, PrivateHierarchicalDeterministicFactorSource)> { + Self::create_new_profile_with_bdfs(clients, None).await + } + + async fn create_new_profile_with_bdfs( + clients: &Clients, + mnemonic_with_passphrase: Option, + ) -> Result<(Profile, PrivateHierarchicalDeterministicFactorSource)> { + debug!("Creating new Profile and BDFS"); + + let device_info = Self::get_device_info(clients).await?; + + let is_main = true; + let private_bdfs = match mnemonic_with_passphrase { + Some(mwp) => { + debug!("Using specified MnemonicWithPassphrase, perhaps we are running in at test..."); + + PrivateHierarchicalDeterministicFactorSource::new_babylon_with_mnemonic_with_passphrase(is_main, mwp, &device_info) + } + None => { + debug!("Generating mnemonic (using Host provided entropy) for a new 'Babylon' `DeviceFactorSource` ('BDFS')"); + + let entropy = clients.entropy.bip39_entropy(); + + PrivateHierarchicalDeterministicFactorSource::new_babylon_with_entropy( + is_main, + entropy, + BIP39Passphrase::default(), + &device_info + )? + } + }; + debug!("Created BDFS (unsaved)"); + + debug!("Creating new Profile..."); + let profile = Profile::from_device_factor_source( + private_bdfs.factor_source.clone(), + device_info, + ); + info!("Created new (unsaved) Profile with ID {}", profile.id()); + Ok((profile, private_bdfs)) + } + + pub async fn device_info(&self) -> Result { + Self::get_device_info(&self.clients).await + } + + pub async fn get_device_info(clients: &Clients) -> Result { + debug!("Get device info"); + let secure_storage = &clients.secure_storage; + + let device_info = match secure_storage.load_device_info().await? { + Some(loaded_device_info) => { + debug!("Found saved device info: {:?}", &loaded_device_info); + loaded_device_info + } + None => { + debug!("Found no saved device info, creating new."); + let new_device_info = clients.host.create_device_info().await; + debug!("Created new device info: {:?}", &new_device_info); + secure_storage.save_device_info(&new_device_info).await?; + debug!("Saved new device info"); + new_device_info + } + }; + + Ok(device_info) + } +} + +#[cfg(test)] +pub const SARGON_OS_TEST_MAX_ASYNC_DURATION: std::time::Duration = + std::time::Duration::from_millis(50); + +#[cfg(test)] +impl SargonOS { + pub async fn with_timeout<'a, F, Fut, T>(&'a self, func: F) -> T + where + F: Fn(&'a SargonOS) -> Fut, + Fut: std::future::Future, + { + let sut = func(self); + actix_rt::time::timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, sut) + .await + .unwrap() + } + + pub async fn boot_test() -> Result> { + Self::boot_test_with_bdfs_mnemonic(None).await + } + + pub async fn boot_test_with_bdfs_mnemonic( + bdfs_mnemonic: impl Into>, + ) -> Result> { + let test_drivers = Drivers::test(); + let bios = Bios::new(test_drivers); + Self::boot_with_bdfs(bios, bdfs_mnemonic.into()).await + } + + pub async fn fast_boot() -> Arc { + Self::fast_boot_bdfs(None).await + } + + pub async fn fast_boot_bdfs( + bdfs_mnemonic: impl Into>, + ) -> Arc { + let req = Self::boot_test_with_bdfs_mnemonic(bdfs_mnemonic); + + actix_rt::time::timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, req) + .await + .unwrap() + .unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use actix_rt::time::timeout; + + #[allow(clippy::upper_case_acronyms)] + type SUT = SargonOS; + + #[actix_rt::test] + async fn test_new_profile_is_active_profile() { + // ARRANGE (and ACT) + let os = SUT::fast_boot().await; + + // ASSERT + let active_profile_id = os + .with_timeout(|x| x.secure_storage.load_active_profile_id()) + .await + .unwrap() + .unwrap(); + + assert_eq!(active_profile_id, os.profile().id()); + } + + #[actix_rt::test] + async fn test_boot_with_existing_profile_is_profile_held() { + // ARRANGE (and ACT) + let secure_storage_driver = EphemeralSecureStorage::new(); + let profile = Profile::sample(); + let secure_storage_client = + SecureStorageClient::new(secure_storage_driver.clone()); + secure_storage_client.save_profile(&profile).await.unwrap(); + secure_storage_client + .save_active_profile_id(profile.id()) + .await + .unwrap(); + let drivers = Drivers::with_secure_storage(secure_storage_driver); + let bios = Bios::new(drivers); + + // ACT + let os = timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, SUT::boot(bios)) + .await + .unwrap() + .unwrap(); + + // ASSERT + let active_profile = os.profile(); + assert_eq!(active_profile.id(), profile.id()); + } + + #[actix_rt::test] + async fn test_boot_with_existing_unowned_profile_cannot_be_mutated() { + // ARRANGE (and ACT) + let secure_storage_driver = EphemeralSecureStorage::new(); + let profile = Profile::sample(); + let secure_storage_client = + SecureStorageClient::new(secure_storage_driver.clone()); + secure_storage_client.save_profile(&profile).await.unwrap(); + secure_storage_client + .save_active_profile_id(profile.id()) + .await + .unwrap(); + let drivers = Drivers::with_secure_storage(secure_storage_driver); + let bios = Bios::new(drivers); + + let os = timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, SUT::boot(bios)) + .await + .unwrap() + .unwrap(); + + let device_info = os.with_timeout(|x| x.device_info()).await.unwrap(); + + // ACT + let add_res = + os.with_timeout(|x| x.add_account(Account::sample())).await; + + // ASSERT + assert_eq!( + add_res, + Err(CommonError::ProfileLastUsedOnOtherDevice { + other_device_id: profile + .header + .last_used_on_device + .id + .to_string(), + this_device_id: device_info.id.to_string() + }) + ); + } + + #[actix_rt::test] + async fn test_boot_with_existing_unowned_profile_is_not_mutated_if_tried_to( + ) { + // ARRANGE (and ACT) + let secure_storage_driver = EphemeralSecureStorage::new(); + let profile = Profile::new(Mnemonic::sample(), DeviceInfo::sample()); + let secure_storage_client = + SecureStorageClient::new(secure_storage_driver.clone()); + secure_storage_client.save_profile(&profile).await.unwrap(); + secure_storage_client + .save_active_profile_id(profile.id()) + .await + .unwrap(); + let drivers = Drivers::with_secure_storage(secure_storage_driver); + let bios = Bios::new(drivers); + + let os = timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, SUT::boot(bios)) + .await + .unwrap() + .unwrap(); + + let new_account = Account::sample_stokenet(); + // ACT + let _ = os + .with_timeout(|x| x.add_account(new_account.clone())) + .await; + + // ASSERT + assert_eq!(os.profile(), profile.clone()); // not changed in memory + + let loaded_profile = os + .with_timeout(|x| x.secure_storage.load_active_profile()) + .await + .unwrap() + .unwrap(); + assert_eq!(loaded_profile, profile); // not changed in secure storage + } + + #[actix_rt::test] + async fn test_boot_with_existing_unowned_profile_when_claimed_can_be_changed( + ) { + // ARRANGE (and ACT) + let secure_storage_driver = EphemeralSecureStorage::new(); + let profile = Profile::new(Mnemonic::sample(), DeviceInfo::sample()); + let secure_storage_client = + SecureStorageClient::new(secure_storage_driver.clone()); + secure_storage_client.save_profile(&profile).await.unwrap(); + secure_storage_client + .save_active_profile_id(profile.id()) + .await + .unwrap(); + let drivers = Drivers::with_secure_storage(secure_storage_driver); + let bios = Bios::new(drivers); + + let os = timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, SUT::boot(bios)) + .await + .unwrap() + .unwrap(); + + let new_account = Account::sample_stokenet(); + // ACT + let claim_was_needed = + os.with_timeout(|x| x.claim_active_profile()).await.unwrap(); + let _ = os + .with_timeout(|x| x.add_account(new_account.clone())) + .await; + + // ASSERT + assert!(claim_was_needed); + assert_ne!(os.profile(), profile.clone()); // was changed in memory + assert_eq!( + os.profile() + .networks + .get_id(NetworkID::Stokenet) + .unwrap() + .accounts[0], + new_account.clone() + ); + + let loaded_profile = os + .with_timeout(|x| x.secure_storage.load_active_profile()) + .await + .unwrap() + .unwrap(); + assert_ne!(loaded_profile.clone(), profile); // was changed in secure storage + + assert_eq!( + loaded_profile + .networks + .get_id(NetworkID::Stokenet) + .unwrap() + .accounts[0], + new_account.clone() + ); + } + + #[actix_rt::test] + async fn test_boot_not_owned_emits_event() { + // ARRANGE (and ACT) + let secure_storage_driver = EphemeralSecureStorage::new(); + let event_bus_driver = RustEventBusDriver::new(); + let profile = Profile::sample(); + let secure_storage_client = + SecureStorageClient::new(secure_storage_driver.clone()); + secure_storage_client.save_profile(&profile).await.unwrap(); + secure_storage_client + .save_active_profile_id(profile.id()) + .await + .unwrap(); + let drivers = Drivers::new( + RustNetworkingDriver::new(), + secure_storage_driver.clone(), + RustEntropyDriver::new(), + RustHostInfoDriver::new(), + RustLoggingDriver::new(), + event_bus_driver.clone(), + RustFileSystemDriver::new(), + EphemeralUnsafeStorage::new(), + ); + let bios = Bios::new(drivers); + + // ACT + let _ = timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, SUT::boot(bios)) + .await + .unwrap() + .unwrap(); + + // ASSERT + assert!( + event_bus_driver + .recorded() + .iter() + .any(|e| e.event.kind() + == EventKind::ProfileLastUsedOnOtherDevice) + ); + } + + #[actix_rt::test] + async fn can_read_out_driver_after_os_is_created() { + // ARRANGE (and ACT) + let event_bus = RustEventBusDriver::new(); + + let drivers = Drivers::with_event_bus(event_bus.clone()); + let bios = Bios::new(drivers); + + // ACT + let sut = timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, SUT::boot(bios)) + .await + .unwrap() + .unwrap(); + + // ASSERT + assert_eq!( + Arc::as_ptr(&sut.drivers().event_bus()), + Arc::as_ptr(&event_bus) + ); + } + + #[actix_rt::test] + async fn test_boot_with_existing_profile_active_profile_id() { + // ARRANGE (and ACT) + let secure_storage_driver = EphemeralSecureStorage::new(); + let profile = Profile::sample(); + let secure_storage_client = + SecureStorageClient::new(secure_storage_driver.clone()); + secure_storage_client.save_profile(&profile).await.unwrap(); + secure_storage_client + .save_active_profile_id(profile.id()) + .await + .unwrap(); + let drivers = Drivers::with_secure_storage(secure_storage_driver); + let bios = Bios::new(drivers); + + // ACT + let os = timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, SUT::boot(bios)) + .await + .unwrap() + .unwrap(); + + // ASSERT + let active_profile_id = os + .with_timeout(|x| x.secure_storage.load_active_profile_id()) + .await + .unwrap() + .unwrap(); + + assert_eq!(active_profile_id, profile.id()); + } + + #[actix_rt::test] + async fn test_change_log_level() { + // ARRANGE (and ACT) + let _ = SUT::fast_boot().await; + + rust_logger_get_all_filters().into_iter().for_each(|l| { + rust_logger_set_level(l); + assert_eq!(rust_logger_get_level(), l); + println!( + "Testing logging at every level with log level set to: {:?}", + l + ); + rust_logger_log_at_every_level() + }); + } +} diff --git a/crates/sargon/src/sargon_os/sargon_os_accounts.rs b/crates/sargon/src/sargon_os/sargon_os_accounts.rs new file mode 100644 index 000000000..6d04c8ad6 --- /dev/null +++ b/crates/sargon/src/sargon_os/sargon_os_accounts.rs @@ -0,0 +1,818 @@ +use crate::prelude::*; + +// ================== +// Create Unsaved Account(s) +// ================== +#[uniffi::export] +impl SargonOS { + /// Returns the non-hidden accounts on the current network, empty if no accounts + /// on the network + pub fn accounts_on_current_network(&self) -> Accounts { + self.profile_holder.accounts_on_current_network().clone() + } + + /// Returns the non-hidden accounts on the current network as `AccountForDisplay` + pub fn accounts_for_display_on_current_network( + &self, + ) -> AccountsForDisplay { + self.profile_holder + .accounts_for_display_on_current_network() + } + + /// Looks up the account by account address, returns Err if the account is + /// unknown, will return a hidden account if queried for. + pub fn account_by_address( + &self, + address: AccountAddress, + ) -> Result { + self.profile_holder.account_by_address(address) + } + + /// Creates a new unsaved mainnet account named "Unnamed {N}", where `N` is the + /// index of the next account for the BDFS. + pub async fn create_unsaved_unnamed_mainnet_account( + &self, + ) -> Result { + let accounts = self + .batch_create_unsaved_accounts( + NetworkID::Mainnet, + 1, + "Unnamed".to_owned(), + ) + .await?; + + Ok(accounts.first().unwrap().clone()) + } + + /// Uses `create_unsaved_account` specifying `NetworkID::Mainnet`. + pub async fn create_unsaved_mainnet_account( + &self, + name: DisplayName, + ) -> Result { + self.create_unsaved_account(NetworkID::Mainnet, name).await + } + + /// Creates a new non securified account **WITHOUT** add it to Profile, using the *main* "Babylon" + /// `DeviceFactorSource` and the "next" index for this FactorSource as derivation path. + /// + /// If you want to add it to Profile, call `wallet.add_account(account)` + pub async fn create_unsaved_account( + &self, + network_id: NetworkID, + name: DisplayName, + ) -> Result { + let profile = self.profile(); + profile + .create_unsaved_account(network_id, name, async move |fs| { + self.load_private_device_factor_source(&fs).await + }) + .await + } + + /// Create a new mainnet Account named "Unnamed" and adds it to the active Profile. + pub async fn create_and_save_new_unnamed_mainnet_account( + &self, + ) -> Result { + self.create_and_save_new_mainnet_account( + DisplayName::new("Unnamed").unwrap(), + ) + .await + } + + /// Create a new mainnet Account and adds it to the active Profile. + pub async fn create_and_save_new_mainnet_account( + &self, + name: DisplayName, + ) -> Result { + self.create_and_save_new_account(NetworkID::Mainnet, name) + .await + } + + /// Create a new Account and adds it to the active Profile. + pub async fn create_and_save_new_account( + &self, + network_id: NetworkID, + name: DisplayName, + ) -> Result { + debug!("Creating account."); + let account = self.create_unsaved_account(network_id, name).await?; + debug!("Created account, now saving it to profile."); + self.add_account(account.clone()).await?; + info!( + "Created account and saved new account into profile, address: {}", + account.address + ); + Ok(account) + } + + /// The account names will be ` ` + pub async fn batch_create_many_accounts_then_save_once( + &self, + count: u16, + network_id: NetworkID, + name_prefix: String, + ) -> Result<()> { + debug!("Batch creating #{} accounts.", count); + let accounts = self + .batch_create_unsaved_accounts(network_id, count, name_prefix) + .await?; + debug!("Created #{} accounts, now saving them to profile.", count); + self.add_accounts(accounts).await?; + info!( + "Created account and saved #{} new accounts into profile", + count + ); + Ok(()) + } + + /// Creates many new non securified accounts **WITHOUT** add them to Profile, using the *main* "Babylon" + /// `DeviceFactorSource` and the "next" indices for this FactorSource as derivation paths. + /// + /// If you want to add them to Profile, call `add_accounts(accounts)` + pub async fn batch_create_unsaved_accounts( + &self, + network_id: NetworkID, + count: u16, + name_prefix: String, + ) -> Result { + let profile = self.profile(); + profile + .create_unsaved_accounts( + network_id, + count, + |idx| { + DisplayName::new(format!("{} {}", name_prefix, idx)) + .expect("Should not use a long name_prefix") + }, + async move |fs| { + self.load_private_device_factor_source(&fs).await + }, + ) + .await + } +} + +// ================== +// Add (Save) Account(s) +// ================== +#[uniffi::export] +impl SargonOS { + /// Add the `account` to active profile and **saves** the updated profile to + /// secure storage. + /// + /// Returns `Ok(())` if the `account` was new and successfully added. If + /// saving failed or if the account was already present in Profile, an + /// error is returned. + pub async fn add_account(&self, account: Account) -> Result<()> { + let address = account.address; + + debug!("Adding account address: {} to profile", address); + self.add_accounts_without_emitting_event(Accounts::just(account)) + .await?; + + self.profile_holder.access_profile_with(|p| { + let accounts_on_network = p + .networks + .get_id(address.network_id()) + .unwrap() + .accounts + .len(); + debug!( + "Added account address: {} to profile, contains: #{}", + address, accounts_on_network + ); + }); + + self.event_bus + .emit(EventNotification::profile_changed( + EventProfileModified::AddedAccount { address }, + )) + .await; + + Ok(()) + } + + /// Adds the `accounts` to active profile and **saves** the updated profile to + /// secure storage. + /// + /// Returns `Ok(())` if the `accounts` were new and successfully added. If + /// saving failed or if the accounts were already present in Profile, an + /// error is returned. + pub async fn add_accounts(&self, accounts: Accounts) -> Result<()> { + let addresses = accounts + .clone() + .into_iter() + .map(|a| a.address) + .collect_vec(); + + self.add_accounts_without_emitting_event(accounts).await?; + + self.event_bus + .emit(EventNotification::profile_changed( + EventProfileModified::AddedAccounts { addresses }, + )) + .await; + + Ok(()) + } +} + +// ================== +// Update Account(s) +// ================== +#[uniffi::export] +impl SargonOS { + pub async fn update_account(&self, updated: Account) -> Result<()> { + self.update_profile_with(|mut p| { + if p.update_account(&updated.address, |old| *old = updated.clone()) + .is_none() + { + Err(CommonError::UnknownAccount) + } else { + Ok(()) + } + }) + .await?; + + self.event_bus + .emit(EventNotification::profile_changed( + EventProfileModified::UpdatedAccount { + address: updated.address, + }, + )) + .await; + + Ok(()) + } +} + +impl SargonOS { + /// Adds the `accounts` to active profile and **saves** the updated profile to + /// secure storage. + /// + /// Returns `Ok(())` if the `accounts` were new and successfully added. If + /// saving failed or if the accounts were already present in Profile, an + /// error is returned. + async fn add_accounts_without_emitting_event( + &self, + accounts: Accounts, + ) -> Result<()> { + if accounts.is_empty() { + warn!("Tried to add empty accounts..."); + return Ok(()); + } + + let number_of_accounts_to_add = accounts.len(); + + let network_id = accounts + .assert_elements_on_same_network()? + .expect("Should have handled empty accounts case already."); + + debug!("Adding #{} accounts to Profile Network with ID: {} - or creating a Profile Network if it does not exist", number_of_accounts_to_add, network_id); + + self.update_profile_with(|mut p| { + let networks = &mut p.networks; + + if networks.contains_id(network_id) { + debug!("Profile already contained network to add #{} account(s) to, network_id: {}", number_of_accounts_to_add, network_id); + networks + .try_try_update_with(&network_id, |network| { + let count_before = network.accounts.len(); + debug!("Profile Network to add #{} account(s) to contains #{} accounts (before adding).", number_of_accounts_to_add, count_before); + network.accounts.extend(accounts.clone()); + let count_after = network.accounts.len(); + debug!("Profile Network now contains: #{} accounts", count_after); + if network.accounts.len() == count_before + number_of_accounts_to_add { + Ok(()) + } else { + Err(CommonError::UnableToAddAllAccountsDuplicatesFound) + } + }) + } else { + debug!("No Profile Network exists with ID {}, creating it...", network_id); + let network = ProfileNetwork::new( + network_id, + accounts.clone(), + Personas::default(), + AuthorizedDapps::default(), + ); + networks.append(network); + Ok(()) + } + }) + .await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use actix_rt::time::timeout; + + #[allow(clippy::upper_case_acronyms)] + type SUT = SargonOS; + + #[actix_rt::test] + async fn test_first_add_account() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + os.with_timeout(|x| x.add_account(Account::sample())) + .await + .unwrap(); + + // ASSERT + assert_eq!(os.profile().networks[0].accounts.len(), 1); + } + + #[actix_rt::test] + async fn test_content_hint_is_updated_when_accounts_are_added() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + os.with_timeout(|x| x.add_account(Account::sample())) + .await + .unwrap(); + + // ASSERT + assert_eq!( + os.profile() + .header + .content_hint + .number_of_accounts_on_all_networks_in_total, + 1 + ); + assert_eq!(os.profile().header.content_hint.number_of_networks, 1); + } + + #[actix_rt::test] + async fn test_first_create_unsaved_account() { + // ARRANGE + let os = SUT::fast_boot_bdfs(MnemonicWithPassphrase::sample()).await; + + // ACT + let unsaved_account = os + .with_timeout(|x| { + x.create_unsaved_mainnet_account( + DisplayName::new("Alice").unwrap(), + ) + }) + .await + .unwrap(); + + // ASSERT + assert_eq!(unsaved_account, Account::sample()); + assert_eq!(os.profile().networks[0].accounts.len(), 0); // not added + } + + #[actix_rt::test] + async fn test_create_unsaved_account_twice_yield_same_accounts() { + // ARRANGE + let os = SUT::fast_boot_bdfs(MnemonicWithPassphrase::sample()).await; + + // ACT + let first = os + .with_timeout(|x| x.create_unsaved_unnamed_mainnet_account()) + .await + .unwrap(); + + let second = os + .with_timeout(|x| x.create_unsaved_unnamed_mainnet_account()) + .await + .unwrap(); + + // ASSERT + assert_eq!(first, second); + } + + #[actix_rt::test] + async fn test_first_create_and_add_account_is_added() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let account = os + .with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + .await + .unwrap(); + + // ASSERT + assert_eq!(os.profile().networks[0].accounts, Accounts::just(account)); + } + + #[actix_rt::test] + async fn test_first_create_and_add_account_has_index_0() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let account = os + .with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + .await + .unwrap(); + + // ASSERT + assert_eq!( + account + .security_state + .into_unsecured() + .unwrap() + .transaction_signing + .derivation_path() + .hd_path() + .components + .last() + .unwrap() + .index(), + 0 + ); + } + + #[actix_rt::test] + async fn test_second_create_and_add_account_has_index_1() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let _ = os + .with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + .await + .unwrap(); + + let second = os + .with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + .await + .unwrap(); + + // ASSERT + assert_eq!( + second + .security_state + .into_unsecured() + .unwrap() + .transaction_signing + .derivation_path() + .hd_path() + .components + .last() + .unwrap() + .index(), + 1 + ); + } + + #[actix_rt::test] + async fn batch_create_account_then_n_accounts_are_saved_and_have_indices_0_through_n( + ) { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let n: u32 = 3; + os.with_timeout(|x| { + x.batch_create_many_accounts_then_save_once( + n as u16, + NetworkID::Mainnet, + "test".to_owned(), + ) + }) + .await + .unwrap(); + + // ASSERT + let indices = os.profile().networks[0] + .accounts + .iter() + .map(|x| { + x.security_state + .into_unsecured() + .unwrap() + .transaction_signing + .derivation_path() + .hd_path() + .components + .last() + .unwrap() + .index() + }) + .collect_vec(); + assert_eq!(indices, (0u32..n).collect_vec()); + } + + #[actix_rt::test] + async fn test_batch_create_and_add_account_n_has_names_with_index_appended_to_prefix( + ) { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let n: u32 = 3; + os.with_timeout(|x| { + x.batch_create_many_accounts_then_save_once( + n as u16, + NetworkID::Mainnet, + "test".to_owned(), + ) + }) + .await + .unwrap(); + + // ASSERT + let names = os.profile().networks[0] + .accounts + .iter() + .map(|x| x.display_name.value.clone()) + .collect_vec(); + + assert_eq!( + names, + ["test 0", "test 1", "test 2"] + .into_iter() + .map(|x| x.to_owned()) + .collect_vec() + ); + } + + #[actix_rt::test] + async fn batch_create_account_then_n_accounts_are_saved_and_have_appearance_id_0_through_max( + ) { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let n = AppearanceID::all().len() as u32 * 2; + os.with_timeout(|x| { + x.batch_create_many_accounts_then_save_once( + n as u16, + NetworkID::Mainnet, + "test".to_owned(), + ) + }) + .await + .unwrap(); + + // ASSERT + let appearance_ids = os.profile().networks[0] + .accounts + .iter() + .map(|x| x.appearance_id) + .collect_vec(); + + assert_eq!( + appearance_ids, + [AppearanceID::all(), AppearanceID::all()].concat() + ); + } + + #[actix_rt::test] + async fn batch_create_account_unsaved_are_not_saved() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + os.with_timeout(|x| { + x.batch_create_unsaved_accounts( + NetworkID::Mainnet, + 3, + "test".to_owned(), + ) + }) + .await + .unwrap(); + + // ASSERT + assert!(os.profile().networks[0].accounts.is_empty()) + } + + trait RandomValue { + fn random() -> Self; + } + impl RandomValue for DisplayName { + fn random() -> Self { + Self::new(format!( + "random-{}", + id().to_string().drain(0..20).collect::() + )) + .unwrap() + } + } + + #[actix_rt::test] + async fn update_account_updates_in_memory_profile() { + // ARRANGE + let os = SUT::fast_boot().await; + + let mut account = Account::sample(); + os.with_timeout(|x| x.add_account(account.clone())) + .await + .unwrap(); + + // ACT + account.display_name = DisplayName::random(); + os.with_timeout(|x| x.update_account(account.clone())) + .await + .unwrap(); + + // ASSERT + assert_eq!(os.profile().networks[0].accounts[0], account.clone()) + } + + #[actix_rt::test] + async fn update_account_updates_saved_profile() { + // ARRANGE + let os = SUT::fast_boot().await; + + let mut account = Account::sample(); + os.with_timeout(|x| x.add_account(account.clone())) + .await + .unwrap(); + + // ACT + account.display_name = DisplayName::random(); + os.with_timeout(|x| x.update_account(account.clone())) + .await + .unwrap(); + + // ASSERT + let saved_profile = os + .with_timeout(|x| x.secure_storage.load_active_profile()) + .await + .unwrap() + .unwrap(); + + assert_eq!(saved_profile.networks[0].accounts[0], account.clone()) + } + + #[actix_rt::test] + async fn test_update_account_emits() { + // ARRANGE (and ACT) + let event_bus_driver = RustEventBusDriver::new(); + let drivers = Drivers::with_event_bus(event_bus_driver.clone()); + let bios = Bios::new(drivers); + + let os = timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, SUT::boot(bios)) + .await + .unwrap() + .unwrap(); + + let mut account = Account::sample(); + os.with_timeout(|x| x.add_account(account.clone())) + .await + .unwrap(); + + // ACT + account.display_name = DisplayName::random(); + os.with_timeout(|x| x.update_account(account.clone())) + .await + .unwrap(); + + // ASSERT + assert!(event_bus_driver + .recorded() + .iter() + .any(|e| e.event.kind() == EventKind::UpdatedAccount)); + } + + #[actix_rt::test] + async fn update_account_unknown_accounts_throws() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let result = os + .with_timeout(|x| x.update_account(Account::sample())) + .await; + + // ASSERT + assert_eq!(result, Err(CommonError::UnknownAccount)) + } + + #[actix_rt::test] + async fn add_accounts_empty_is_ok() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let result = os.with_timeout(|x| x.add_accounts(Accounts::new())).await; + + // ASSERT + assert!(result.is_ok()) + } + + #[actix_rt::test] + async fn add_accounts_duplicates_throws() { + // ARRANGE + let os = SUT::fast_boot().await; + + let account = Account::sample(); + os.with_timeout(|x| x.add_account(account.clone())) + .await + .unwrap(); + + // ACT + let result = os + .with_timeout(|x| x.add_accounts(Accounts::just(account.clone()))) + .await; + + // ASSERT + assert_eq!( + result, + Err(CommonError::UnableToAddAllAccountsDuplicatesFound) + ) + } + + #[actix_rt::test] + async fn test_accounts_on_current_network_empty() { + let os = SUT::fast_boot().await; + assert_eq!(os.accounts_on_current_network(), Accounts::new()); + } + + #[actix_rt::test] + async fn test_accounts_on_current_network_non_empty() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let account = os + .with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + .await + .unwrap(); + + // ASSERT + assert_eq!(os.accounts_on_current_network(), Accounts::just(account)); + } + + #[actix_rt::test] + async fn test_accounts_on_current_network_empty_when_switched_network() { + // ARRANGE + let os = SUT::fast_boot().await; + + let _ = os + .with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + .await + .unwrap(); + + // ACT + let _ = os + .with_timeout(|x| x.change_current_gateway(Gateway::stokenet())) + .await + .unwrap(); + + // ASSERT + assert_eq!(os.accounts_on_current_network(), Accounts::new()); + } + + #[actix_rt::test] + async fn test_accounts_for_display_on_current_network() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let account = os + .with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + .await + .unwrap(); + + // ASSERT + assert_eq!( + os.accounts_for_display_on_current_network(), + AccountsForDisplay::just(AccountForDisplay::from(account)) + ); + } + + #[actix_rt::test] + async fn test_account_by_address_exists() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let account = os + .with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + .await + .unwrap(); + + // ASSERT + assert_eq!(os.account_by_address(account.address), Ok(account)); + } + + #[actix_rt::test] + async fn test_account_by_address_not_exists() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + // so that we have at least one network (with one account) + let _ = os + .with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + .await + .unwrap(); + + // ASSERT + assert_eq!( + os.account_by_address(AccountAddress::sample_mainnet()), + Err(CommonError::UnknownAccount) + ); + } +} diff --git a/src/wallet/wallet_device_factor_sources.rs b/crates/sargon/src/sargon_os/sargon_os_factors.rs similarity index 50% rename from src/wallet/wallet_device_factor_sources.rs rename to crates/sargon/src/sargon_os/sargon_os_factors.rs index c7979aa04..4831f5033 100644 --- a/src/wallet/wallet_device_factor_sources.rs +++ b/crates/sargon/src/sargon_os/sargon_os_factors.rs @@ -1,13 +1,40 @@ use crate::prelude::*; -impl Wallet { +#[uniffi::export] +impl SargonOS { + /// 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 async fn load_private_device_factor_source_by_id( + &self, + id: &FactorSourceIDFromHash, + ) -> Result { + let device_factor_source = self + .profile_holder + .access_profile_with(|p| p.device_factor_source_by_id(id))?; + self.load_private_device_factor_source(&device_factor_source) + .await + } + + pub fn bdfs(&self) -> DeviceFactorSource { + self.profile_holder.access_profile_with(|p| p.bdfs()) + } +} + +impl SargonOS { /// Tries to load a `MnemonicWithPassphrase` from secure storage /// by `id` of type `FactorSourceIDFromHash`. - pub fn mnemonic_with_passphrase_of_device_factor_source_by_id( + pub async fn mnemonic_with_passphrase_of_device_factor_source_by_id( &self, id: &FactorSourceIDFromHash, ) -> Result { - self.wallet_client_storage.load_mnemonic_with_passphrase(id) + self.secure_storage.load_mnemonic_with_passphrase(id).await } /// Loads a `MnemonicWithPassphrase` with the `id` of `device_factor_source`, @@ -19,7 +46,7 @@ impl Wallet { /// /// Returns `Err` if loading or decoding of `MnemonicWithPassphrase` from /// SecureStorage fails. - pub fn load_private_device_factor_source( + pub async fn load_private_device_factor_source( &self, device_factor_source: &DeviceFactorSource, ) -> Result { @@ -30,6 +57,7 @@ impl Wallet { self.mnemonic_with_passphrase_of_device_factor_source_by_id( &device_factor_source.id, ) + .await .map(|mwp| { PrivateHierarchicalDeterministicFactorSource::new( mwp, @@ -41,86 +69,94 @@ impl Wallet { ) } - /// 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( + pub async fn mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id( &self, factor_source_id: &FactorSourceID, ) -> Result { - factor_source_id + let id = factor_source_id .clone() .into_hash() - .map_err(|_| CommonError::FactorSourceIDNotFromHash) - .and_then(|id| { - self.mnemonic_with_passphrase_of_device_factor_source_by_id(&id) - }) + .map_err(|_| CommonError::FactorSourceIDNotFromHash)?; + self.mnemonic_with_passphrase_of_device_factor_source_by_id(&id) + // tarpaulin will incorrectly flag next line is missed + .await } /// Tries to load the `MnemonicWithPassphrase` for the main "Babylon" /// `DeviceFactorSource` from secure storage. - pub fn main_bdfs_mnemonic_with_passphrase( + pub async fn main_bdfs_mnemonic_with_passphrase( &self, ) -> Result { - let profile = &self.profile(); - let bdfs = profile.bdfs(); + let bdfs = self.profile_holder.access_profile_with(|p| p.bdfs()); self.mnemonic_with_passphrase_of_device_factor_source_by_id(&bdfs.id) + // tarpaulin will incorrectly flag next line is missed + .await } } #[cfg(test)] mod tests { - use crate::prelude::*; - - #[test] - fn main_bdfs_mnemonic_with_passphrase() { - let private = PrivateHierarchicalDeterministicFactorSource::sample(); - let dfs = private.factor_source; - let profile = Profile::sample(); - 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, - }; - storage.save_data(key.clone(), data.clone()).unwrap(); - assert_eq!( - wallet.main_bdfs_mnemonic_with_passphrase().unwrap(), - MnemonicWithPassphrase::sample() - ); + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = SargonOS; + + #[actix_rt::test] + async fn test_load_private_device_factor_source_by_id() { + // ARRANGE + let mwp = MnemonicWithPassphrase::sample(); + let factor_source_id = FactorSourceIDFromHash::new_for_device(&mwp); + let os = SUT::fast_boot_bdfs(mwp.clone()).await; + + // ACT + let private = os + .with_timeout(|x| { + x.load_private_device_factor_source_by_id(&factor_source_id) + }) + .await + .unwrap(); + + // ASSERT + assert_eq!(private.mnemonic_with_passphrase, mwp); + } + + #[actix_rt::test] + async fn test_bdfs() { + // ARRANGE + let mwp = MnemonicWithPassphrase::sample(); + let os = SUT::fast_boot_bdfs(mwp.clone()).await; + + // ACT + let loaded = os.bdfs(); + + // ASSERT assert_eq!( - wallet.mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id(&dfs.factor_source_id()).unwrap(), - MnemonicWithPassphrase::sample() + loaded.factor_source_id(), + FactorSourceIDFromHash::new_for_device(&mwp).into() ); } - #[test] - fn mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id_fail_not_factor_source_id_from_hash( + #[actix_rt::test] + async fn test_mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id( ) { - let (wallet, _) = Wallet::ephemeral(Profile::sample()); - assert_eq!( - wallet.mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id(&FactorSourceIDFromAddress::sample().into()), - Err(CommonError::FactorSourceIDNotFromHash) - ); + // ARRANGE + let mwp = MnemonicWithPassphrase::sample(); + let factor_source_id = FactorSourceIDFromHash::new_for_device(&mwp); + let id = FactorSourceID::from(factor_source_id); + let os = SUT::fast_boot_bdfs(mwp.clone()).await; + + // ACT + let loaded = os + .with_timeout(|x| { + x.mnemonic_with_passphrase_of_device_factor_source_by_factor_source_id(&id) + }) + .await + .unwrap(); + + // ASSERT + assert_eq!(loaded, mwp); } } diff --git a/crates/sargon/src/sargon_os/sargon_os_gateway.rs b/crates/sargon/src/sargon_os/sargon_os_gateway.rs new file mode 100644 index 000000000..b03051ee9 --- /dev/null +++ b/crates/sargon/src/sargon_os/sargon_os_gateway.rs @@ -0,0 +1,191 @@ +use crate::prelude::*; + +// ================== +// Get Current Gateway/Network +// ================== +#[uniffi::export] +impl SargonOS { + pub fn current_network_id(&self) -> NetworkID { + self.profile_holder.current_network_id() + } + + pub fn current_gateway(&self) -> Gateway { + self.profile_holder.current_gateway().clone() + } + + pub fn gateways(&self) -> SavedGateways { + self.profile_holder.gateways().clone() + } + + pub fn current_network(&self) -> ProfileNetwork { + self.profile_holder.current_network().clone() + } +} + +// ================== +// Change Current Gateway +// ================== +#[uniffi::export] +impl SargonOS { + /// Changes the current Gateway to `to`, if it is not already the current. + /// Returns the outcome of the change, if we did in fact switch (current != to), + /// and if we switched then if `to` as new. + /// + /// If we did in fact change current, an `EventNotification` is emitted. + pub async fn change_current_gateway( + &self, + to: Gateway, + ) -> Result { + info!("Changing current gateway to: {}", &to); + let network_id = to.network.id; + let outcome = self + .update_profile_with(|mut p| { + let outcome = + p.app_preferences.gateways.change_current(to.clone()); + match outcome { + ChangeGatewayOutcome::DidChange { is_new: _ } => { + if !p.networks.contains_id(network_id) { + p.networks.append(ProfileNetwork::new_empty_on( + network_id, + )); + } + Ok(outcome) + } + ChangeGatewayOutcome::NoChange => Ok(outcome), + } + }) + .await?; + + match outcome { + ChangeGatewayOutcome::DidChange { is_new } => { + self.event_bus + .emit(EventNotification::new( + Event::GatewayChangedCurrent { to, is_new }, + )) + .await; + } + ChangeGatewayOutcome::NoChange => {} + }; + + debug!("Change current gateway outcome: {:?}", &outcome); + + Ok(outcome) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use actix_rt::time::timeout; + + #[allow(clippy::upper_case_acronyms)] + type SUT = SargonOS; + + #[actix_rt::test] + async fn test_change_gateway_creates_empty_network_if_needed() { + // ARRANGE + let os = SUT::fast_boot().await; + let number_of_networks_before_change = os.profile().networks.len(); + + // ACT + os.with_timeout(|x| x.change_current_gateway(Gateway::stokenet())) + .await + .unwrap(); + + // ASSERT + assert_eq!( + os.profile().networks.len(), + number_of_networks_before_change + 1 + ); + } + + #[actix_rt::test] + async fn test_change_gateway_gateways_returns_updated() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + os.with_timeout(|x| x.change_current_gateway(Gateway::stokenet())) + .await + .unwrap(); + + // ASSERT + assert_eq!(os.gateways().current, Gateway::stokenet()) + } + + #[actix_rt::test] + async fn test_change_gateway_current_returns_updated() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + os.with_timeout(|x| x.change_current_gateway(Gateway::stokenet())) + .await + .unwrap(); + + // ASSERT + assert_eq!(os.current_gateway(), Gateway::stokenet()) + } + + #[actix_rt::test] + async fn test_change_gateway_current_returns_updated_network_id() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + os.with_timeout(|x| x.change_current_gateway(Gateway::stokenet())) + .await + .unwrap(); + + // ASSERT + assert_eq!(os.current_network_id(), NetworkID::Stokenet) + } + + #[actix_rt::test] + async fn test_change_gateway_emits_event() { + // ARRANGE (and ACT) + let event_bus_driver = RustEventBusDriver::new(); + let drivers = Drivers::with_event_bus(event_bus_driver.clone()); + let bios = Bios::new(drivers); + + let os = timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, SUT::boot(bios)) + .await + .unwrap() + .unwrap(); + + // ACT + os.with_timeout(|x| x.change_current_gateway(Gateway::stokenet())) + .await + .unwrap(); + + // ASSERT + assert!(event_bus_driver + .recorded() + .iter() + .any(|e| e.event.kind() == EventKind::GatewayChangedCurrent)); + } + + #[actix_rt::test] + async fn test_change_to_current_gateway_does_not_emits_event() { + // ARRANGE (and ACT) + let event_bus_driver = RustEventBusDriver::new(); + let drivers = Drivers::with_event_bus(event_bus_driver.clone()); + let bios = Bios::new(drivers); + + let os = timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, SUT::boot(bios)) + .await + .unwrap() + .unwrap(); + + // ACT + os.with_timeout(|x| x.change_current_gateway(Gateway::mainnet())) + .await + .unwrap(); + + // ASSERT + assert!(!event_bus_driver + .recorded() + .iter() + .any(|e| e.event.kind() == EventKind::GatewayChangedCurrent)); + } +} diff --git a/crates/sargon/src/sargon_os/sargon_os_profile.rs b/crates/sargon/src/sargon_os/sargon_os_profile.rs new file mode 100644 index 000000000..e59eed842 --- /dev/null +++ b/crates/sargon/src/sargon_os/sargon_os_profile.rs @@ -0,0 +1,677 @@ +#![allow(deprecated)] + +use std::sync::RwLockWriteGuard; + +use crate::prelude::*; + +impl SargonOS { + /// Returns `true` if claim was needed, i.e. if `profile.header.last_used_on_device` was + /// different than `device_info` before claim occurred. + fn claim_provided_profile( + profile: &mut Profile, + device_info: DeviceInfo, + ) -> bool { + let was_needed = profile.header.last_used_on_device != device_info; + profile.update_header(device_info); + was_needed + } + + pub async fn claim_profile(&self, profile: &mut Profile) -> Result<()> { + debug!("Claiming profile, id: {}", &profile.id()); + let device_info = self.device_info().await?; + Self::claim_provided_profile(profile, device_info.clone()); + info!( + "Claimed profile, id: {}, with device info: {}", + &profile.id(), + device_info + ); + Ok(()) + } +} + +#[uniffi::export] +impl SargonOS { + pub fn has_any_network(&self) -> bool { + self.profile_holder + .access_profile_with(|p| !p.networks.is_empty()) + } + + /// Has **any** account, at all, including hidden, on any network. + pub fn has_any_account_on_any_network(&self) -> bool { + self.profile_holder + .access_profile_with(|p| p.has_any_account_on_any_network()) + } + + pub fn profile(&self) -> Profile { + self.profile_holder.profile() + } + + #[allow(non_snake_case)] + #[deprecated( + since = "0.0.1", + note = "Wallet Clients SHOULD migrate to use more specialized methods on SargonOS instead, e.g. `createAndSaveNewAccount`. And SargonOS should be the SOLE object to perform the mutation and persisting." + )] + pub async fn DEPRECATED_save_ffi_changed_profile( + &self, + profile: Profile, + ) -> Result<()> { + if profile.id() != self.profile().id() { + return Err( + CommonError::TriedToUpdateProfileWithOneWithDifferentID, + ); + } + self.update_profile_with(|mut p| { + *p = profile.clone(); + Ok(()) + }) + .await + } + + pub async fn import_profile(&self, profile: Profile) -> Result<()> { + let imported_id = profile.id(); + debug!("Importing profile, id: {}", imported_id); + let mut profile = profile; + self.claim_profile(&mut profile).await?; + + self.secure_storage + .save_profile_and_active_profile_id(&profile) + .await?; + + debug!( + "Saved imported profile into secure storage, id: {}", + imported_id + ); + + self.profile_holder.replace_profile_with(profile)?; + debug!( + "Replaced held profile with imported one, id: {}", + imported_id + ); + + self.event_bus + .emit(EventNotification::new(Event::ImportedProfile { + id: imported_id, + })) + .await; + + info!("Successfully imported profile, id: {}", imported_id); + + Ok(()) + } + + /// Returns `true` if the profile was changed (i.e. if claim was indeed needed), + /// `false`` otherwise. + pub async fn claim_active_profile(&self) -> Result { + let device_info = self.device_info().await?; + self.maybe_validate_ownership_update_profile_with(false, |mut p| { + Ok(Self::claim_provided_profile(&mut p, device_info.clone())) + }) + .await + } + + /// Deletes the profile and the active profile id and all references Device + /// factor sources from secure storage, and creates a new empty profile + /// and a new bdfs, and saves those into secure storage, returns the ID of + /// the new profile. + pub async fn delete_profile_then_create_new_with_bdfs( + &self, + ) -> Result { + let (profile, bdfs) = self + .delete_profile_and_mnemonics_replace_in_memory_without_persisting() + .await?; + let profile_id = profile.id(); + self.secure_storage + .save_private_hd_factor_source(&bdfs) + .await?; + + self.secure_storage + .save_profile_and_active_profile_id(&profile) + .await?; + + Ok(profile_id) + } + + /// Do NOT use in production. Instead use `delete_profile_then_create_new_with_bdfs` + /// in production. This method does not persist the new profile. + pub async fn emulate_fresh_install(&self) -> Result<()> { + warn!("Emulate fresh install of app. Will delete Profile and secrets from secure storage, without saving the new. BAD state."); + let _ = self + .delete_profile_and_mnemonics_replace_in_memory_without_persisting() + .await?; + Ok(()) + } +} + +impl SargonOS { + pub async fn validate_is_allowed_to_mutate_active_profile( + &self, + ) -> Result<()> { + Self::validate_is_allowed_to_update_provided_profile( + &self.clients, + &self.profile(), + ) + .await + } + + pub async fn validate_is_allowed_to_update_provided_profile( + clients: &Clients, + profile: &Profile, + ) -> Result<()> { + Self::check_is_allowed_to_update_provided_profile( + clients, profile, true, + ) + .await?; + Ok(()) + } + + pub async fn check_is_allowed_to_update_provided_profile( + clients: &Clients, + profile: &Profile, + err_on_lack_of_ownership: bool, + ) -> Result { + debug!("Checking if profile.header.last_used_on_device is self.device_info"); + let device_info = Self::get_device_info(clients).await?; + let last_used = profile.header.last_used_on_device.clone(); + if last_used == device_info { + debug!("Ownership check passed (profile.header.last_used_on_device == self.device_info)"); + Ok(true) + } else { + warn!("Profile was last used on another device, will not be able to update it until it has been claimed."); + clients + .event_bus + .emit(EventNotification::profile_used_on_other_device( + last_used.clone(), + )) + .await; + if err_on_lack_of_ownership { + Err(CommonError::ProfileLastUsedOnOtherDevice { + other_device_id: last_used.id.to_string(), + this_device_id: device_info.id.to_string(), + }) + } else { + // used by SargonOS::boot + Ok(false) + } + } + } + + /// Validates ownership of Profile, then updates and **saves** it to + /// secure storage, after mutating it with `mutate`. + pub async fn update_profile_with(&self, mutate: F) -> Result + where + F: Fn(RwLockWriteGuard<'_, Profile>) -> Result, + { + self.maybe_validate_ownership_update_profile_with(true, mutate) + .await + } + + /// Updates and **saves** profile to secure storage, after + /// mutating it with `mutate`, optionally validating ownership of Profile + /// first. + /// + /// The only function to pass `false` to the `validate_ownership` parameter + /// is the `SargonOS::claim_active_profile` method. + pub async fn maybe_validate_ownership_update_profile_with( + &self, + validate_ownership: bool, // should only ever pass `false` from `claim` + mutate: F, + ) -> Result + where + F: Fn(RwLockWriteGuard<'_, Profile>) -> Result, + { + if validate_ownership { + self.validate_is_allowed_to_mutate_active_profile().await?; + } + let res = self.profile_holder.update_profile_with(mutate)?; + self.profile_holder.update_profile_with(|mut p| { + p.update_header(None); + Ok(()) + })?; + self.save_existing_profile() + // tarpaulin will incorrectly flag next line is missed + .await?; + Ok(res) + } + + pub async fn save_existing_profile(&self) -> Result<()> { + self.save_profile(&self.profile()).await + } + + pub async fn save_profile(&self, profile: &Profile) -> Result<()> { + self.validate_is_allowed_to_mutate_active_profile().await?; + + let secure_storage = &self.secure_storage; + + secure_storage + .save( + SecureStorageKey::ProfileSnapshot { + profile_id: profile.header.id, + }, + profile, + ) + .await?; + + self.event_bus + .emit(EventNotification::new(Event::ProfileSaved)) + .await; + + Ok(()) + } + + /// Deletes the profile and the active profile id and all references Device + /// factor sources from secure storage, does **NOT** change the in-memory + /// profile in `profile_holder`. + async fn delete_profile_and_mnemonics(&self) -> Result<()> { + let secure_storage = &self.secure_storage; + let device_factor_sources = self + .profile_holder + .access_profile_with(|p| p.device_factor_sources()); + + for dfs in device_factor_sources.iter() { + secure_storage.delete_mnemonic(&dfs.id).await? + } + + secure_storage.delete_profile(self.profile().id()).await?; + secure_storage.delete_active_profile_id().await?; + Ok(()) + } + + /// Deletes the profile and the active profile id and all references Device + /// factor sources from secure storage, and creates a new empty profile + /// and a new bdfs and replaces the in-memory profile held by profile_holder, + /// **without** persisting the neither the profile nor the new BDFS to secure + /// storage. + /// + /// This method is typically only relevant for testing purposes, emulating a + /// fresh install of wallet apps, wallet apps can call this method and then + /// force quit, which should be equivalent with a fresh install of the app. + pub async fn delete_profile_and_mnemonics_replace_in_memory_without_persisting( + &self, + ) -> Result<(Profile, PrivateHierarchicalDeterministicFactorSource)> { + self.delete_profile_and_mnemonics().await?; + let (profile, bdfs) = self.new_profile_and_bdfs().await?; + self.profile_holder.replace_profile_with(profile.clone())?; + Ok((profile, bdfs)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use actix_rt::time::timeout; + + #[allow(clippy::upper_case_acronyms)] + type SUT = SargonOS; + + #[actix_rt::test] + async fn new_profile_has_a_mainnet_network_which_is_empty() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT - nothing done. + + // ASSERT + assert_eq!( + os.current_network(), + ProfileNetwork::new_empty_on(NetworkID::Mainnet) + ); + } + + #[actix_rt::test] + async fn create_first_account_has_networks_is_true() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + os.with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + .await + .unwrap(); + + // ASSERT + assert!(os.has_any_network()); + } + + #[actix_rt::test] + async fn create_first_account_has_accounts_on_any_network() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + os.with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + .await + .unwrap(); + + // ASSERT + assert!(os.has_any_account_on_any_network()); + } + + #[actix_rt::test] + async fn test_import_profile_is_current_by_id() { + // ARRANGE + let os = SUT::fast_boot().await; + let p = Profile::sample(); + + // ACT + os.with_timeout(|x| x.import_profile(p.clone())) + .await + .unwrap(); + + // ASSERT + assert_eq!(os.profile().id(), p.id()); + } + + #[actix_rt::test] + async fn test_import_profile_emits_event() { + // ARRANGE (and ACT) + let event_bus_driver = RustEventBusDriver::new(); + let drivers = Drivers::with_event_bus(event_bus_driver.clone()); + let bios = Bios::new(drivers); + + let os = timeout(SARGON_OS_TEST_MAX_ASYNC_DURATION, SUT::boot(bios)) + .await + .unwrap() + .unwrap(); + + let p = Profile::sample(); + + // ACT + os.with_timeout(|x| x.import_profile(p.clone())) + .await + .unwrap(); + + // ASSERT + assert!(event_bus_driver + .recorded() + .iter() + .any(|e| e.event.kind() == EventKind::ImportedProfile)); + } + + #[actix_rt::test] + async fn test_import_profile_is_saved_into_storage() { + // ARRANGE + let os = SUT::fast_boot().await; + let p = Profile::sample(); + + // ACT + os.with_timeout(|x| x.import_profile(p.clone())) + .await + .unwrap(); + + // ASSERT + let saved = os + .with_timeout(|x| x.secure_storage.load_active_profile()) + .await + .unwrap() + .unwrap(); + + assert_eq!(saved.id(), p.id()); + } + + #[actix_rt::test] + async fn test_import_profile_last_used_on_device_is_set() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + os.with_timeout(|x| x.import_profile(Profile::sample())) + .await + .unwrap(); + + // ASSERT + let device_info = os.device_info().await.unwrap(); + assert_eq!(os.profile().header.last_used_on_device, device_info); + } + + #[actix_rt::test] + async fn test_import_profile_last_modified_is_set() { + // ARRANGE + let os = SUT::fast_boot().await; + let profile = Profile::sample(); + let last_modified = &profile.header.last_modified; + + // ACT + os.with_timeout(|x| x.import_profile(profile.clone())) + .await + .unwrap(); + + // ASSERT + assert_ne!(&os.profile().header.last_modified, last_modified); + } + + #[actix_rt::test] + async fn test_import_profile_is_claimed_and_can_be_edited() { + // ARRANGE + let os = SUT::fast_boot().await; + let profile = Profile::sample(); + + // ACT + os.with_timeout(|x| x.import_profile(profile.clone())) + .await + .unwrap(); + + let new_account = Account::sample_stokenet_paige(); + os.with_timeout(|x| x.add_account(new_account.clone())) + .await + .unwrap(); + + // ASSERT + assert!(os + .profile() + .networks + .get_id(NetworkID::Stokenet) + .unwrap() + .accounts + .contains_id(new_account.id())); + + let loaded = os + .with_timeout(|x| x.secure_storage.load_active_profile()) + .await + .unwrap() + .unwrap(); + assert!(loaded + .networks + .get_id(NetworkID::Stokenet) + .unwrap() + .accounts + .contains_id(new_account.id())); + } + + #[actix_rt::test] + async fn test_import_profile_active_profile_id_is_set() { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + os.with_timeout(|x| x.import_profile(Profile::sample())) + .await + .unwrap(); + + // ASSERT + let active_profile_id = os + .with_timeout(|x| x.secure_storage.load_active_profile_id()) + .await + .unwrap() + .unwrap(); + + assert_eq!(active_profile_id, os.profile().id()); + } + + #[actix_rt::test] + async fn test_delete_profile_then_create_new_with_bdfs_old_bdfs_is_deleted() + { + // ARRANGE + let bdfs = MnemonicWithPassphrase::sample(); + let os = SUT::fast_boot_bdfs(bdfs.clone()).await; + + // ACT + os.with_timeout(|x| x.delete_profile_then_create_new_with_bdfs()) + .await + .unwrap(); + + // ASSERT + let id = FactorSourceIDFromHash::new_for_device(&bdfs); + let old_bdfs = os + .with_timeout(|x| { + x.secure_storage.load_mnemonic_with_passphrase(&id) + }) + .await; + + assert!(old_bdfs.is_err()); + } + + #[actix_rt::test] + async fn test_delete_profile_then_create_new_with_bdfs_old_profile_is_deleted( + ) { + // ARRANGE + let bdfs = MnemonicWithPassphrase::sample(); + let os = SUT::fast_boot_bdfs(bdfs.clone()).await; + let profile_id = os.profile().id(); + + // ACT + os.with_timeout(|x| x.delete_profile_then_create_new_with_bdfs()) + .await + .unwrap(); + + // ASSERT + let load_old_profile_result = os + .with_timeout(|x| x.secure_storage.load_profile_with_id(profile_id)) + .await; + + assert!(load_old_profile_result.is_err()); + } + + #[actix_rt::test] + async fn test_delete_profile_then_create_new_with_bdfs_new_bdfs_is_saved() { + // ARRANGE + let bdfs = MnemonicWithPassphrase::sample(); + let os = SUT::fast_boot_bdfs(bdfs.clone()).await; + + // ACT + os.with_timeout(|x| x.delete_profile_then_create_new_with_bdfs()) + .await + .unwrap(); + + // ASSERT + let saved_bdfs = os + .with_timeout(|x| x.main_bdfs_mnemonic_with_passphrase()) + .await + .unwrap(); + + assert_ne!(saved_bdfs, bdfs); + } + + #[actix_rt::test] + async fn test_delete_profile_then_create_new_with_bdfs_new_profile_is_saved( + ) { + // ARRANGE + let os = SUT::fast_boot().await; + let profile = Profile::sample(); + os.with_timeout(|x| x.import_profile(profile.clone())) + .await + .unwrap(); + + // ACT + os.with_timeout(|x| x.delete_profile_then_create_new_with_bdfs()) + .await + .unwrap(); + + // ASSERT + let active_profile = os + .with_timeout(|x| x.secure_storage.load_active_profile()) + .await + .unwrap() + .unwrap(); + + assert_ne!(active_profile.id(), profile.id()); + } + + #[actix_rt::test] + async fn test_delete_profile_then_create_new_with_bdfs_device_info_is_unchanged( + ) { + // ARRANGE + let os = SUT::fast_boot().await; + let device_info = os.with_timeout(|x| x.device_info()).await.unwrap(); + assert_eq!(&os.profile().header.creating_device, &device_info); + + // ACT + os.with_timeout(|x| x.delete_profile_then_create_new_with_bdfs()) + .await + .unwrap(); + + // ASSERT + let device_info = os.with_timeout(|x| x.device_info()).await.unwrap(); + assert_eq!(&os.profile().header.creating_device, &device_info); + } + + #[actix_rt::test] + async fn test_emulate_fresh_install_does_not_save_new() { + // ARRANGE + let os = SUT::fast_boot().await; + let first = os.profile().id(); + + // ACT + os.with_timeout(|x| x.emulate_fresh_install()) + .await + .unwrap(); + + // ASSERT + let second = os.profile().id(); + assert_ne!(second, first); + let load_profile_res = os + .with_timeout(|x| x.secure_storage.load_profile_with_id(second)) + .await; + + assert_eq!( + load_profile_res, + Err(CommonError::UnableToLoadProfileFromSecureStorage { + profile_id: second.to_string() + }) + ); + } + + #[actix_rt::test] + async fn test_deprecated_save_ffi_changed_profile() { + // ARRANGE + let os = SUT::fast_boot().await; + + let mut profile = os.profile(); + let new_network = ProfileNetwork::new( + NetworkID::Stokenet, + Accounts::just(Account::sample_stokenet()), + Personas::new(), + AuthorizedDapps::new(), + ); + + profile.networks.append(new_network.clone()); + + // ACT + os.with_timeout(|x| { + x.DEPRECATED_save_ffi_changed_profile(profile.clone()) + }) + .await + .unwrap(); + + // ASSERT + assert_eq!(os.profile().networks, profile.networks); // header has been updated so cannot do full profile comparison. + } + + #[actix_rt::test] + async fn test_deprecated_save_ffi_changed_profile_is_err_when_different_profile_id( + ) { + // ARRANGE + let os = SUT::fast_boot().await; + + // ACT + let res = os + .with_timeout(|x| { + x.DEPRECATED_save_ffi_changed_profile(Profile::sample()) + }) + .await; + + // ASSERT + assert_eq!( + res, + Err(CommonError::TriedToUpdateProfileWithOneWithDifferentID) + ); + } +} diff --git a/crates/sargon/src/subsystems/README.md b/crates/sargon/src/subsystems/README.md new file mode 100644 index 000000000..eb468f546 --- /dev/null +++ b/crates/sargon/src/subsystems/README.md @@ -0,0 +1,6 @@ +# Subsystem + +A subsystem needs a "driver" (just like a "client"), but they don't need to be owned by the SargonOS and is never directly accessed, since they have have a static instance with their own lifecycle. + +An example of a subsystem is the `log` for which we install a Driver from FFI host, and is initialized +during creation of the BIOS, but it is not a client, since we wanna be able to log from anywhere inside of the Sargon crate, not specifically tied to the SargonOS instance. diff --git a/crates/sargon/src/subsystems/log_system/log_system.rs b/crates/sargon/src/subsystems/log_system/log_system.rs new file mode 100644 index 000000000..0c97e8145 --- /dev/null +++ b/crates/sargon/src/subsystems/log_system/log_system.rs @@ -0,0 +1,123 @@ +use std::sync::Once; + +use crate::prelude::*; + +#[derive(Debug)] +struct LogSystem(RwLock>>); + +static LOG: LogSystem = LogSystem(RwLock::new(None)); + +impl log::Log for LogSystem { + fn enabled(&self, _: &log::Metadata<'_>) -> bool { + true + } + + fn log(&self, record: &log::Record<'_>) { + if !self.enabled(record.metadata()) { + return; + } + let msg = record.args().to_string(); + let level = record.level(); + if let Some(driver) = &*self.0.read().unwrap() { + driver.log(LogLevel::from(level), msg) + } + } + + fn flush(&self) {} +} + +fn init() { + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + log::set_logger(&LOG) + .expect("Should always be able to install a logger."); + log::set_max_level(log::LevelFilter::Trace); + }); +} + +pub fn install_logger(logging_driver: Arc) { + init(); + *LOG.0.write().unwrap() = Some(logging_driver); + rust_logger_set_level(LogFilter::Trace); // can be called from FFI later + debug!("Finished installing logger"); +} + +/// Do not call this when you are using the SargonOS, it will have installed +/// a logger already. This is useful in tests which are NOT SargonOS tests, +/// or BIOS tests. +#[uniffi::export] +pub fn rust_logger_init() { + install_logger(RustLoggingDriver::new()) +} + +#[uniffi::export] +pub fn rust_logger_set_level(level: LogFilter) { + let log_level = log::LevelFilter::from(level); + log::set_max_level(log_level); + std::env::set_var( + "RUST_LOG", + std::ffi::OsStr::new(&format!("{:?}", log_level)), + ); +} + +/// Returns every supported LogFilter +#[uniffi::export] +pub fn rust_logger_get_all_filters() -> Vec { + all::().collect() +} + +/// Returns every supported LogLevel +#[uniffi::export] +pub fn rust_logger_get_all_levels() -> Vec { + all::().collect() +} + +#[uniffi::export] +pub fn rust_logger_get_level() -> LogFilter { + LogFilter::from(log::max_level()) +} + +#[uniffi::export] +pub fn rust_logger_log_at_every_level() { + error!("Rust test: 'error'"); + warn!("Rust test: 'warn'"); + info!("Rust test: 'info'"); + debug!("Rust test: 'debug'"); + trace!("Rust test: 'trace'"); +} + +#[cfg(test)] +mod tests { + + use log::Log; + + use super::*; + + #[test] + fn install_rust_logger_change_level() { + install_logger(RustLoggingDriver::new()); + let new = LogFilter::Warn; + rust_logger_set_level(new); + assert_eq!(rust_logger_get_level(), new) + } + + #[test] + fn test_flush() { + let driver = RustLoggingDriver::new(); + install_logger(driver); + LOG.flush(); + } + + #[test] + fn test_rust_logger_init() { + rust_logger_init() + } + + #[test] + fn test_rust_logger_get_all_levels() { + assert_eq!( + rust_logger_get_all_levels(), + all::().collect_vec() + ); + } +} diff --git a/crates/sargon/src/subsystems/log_system/mod.rs b/crates/sargon/src/subsystems/log_system/mod.rs new file mode 100644 index 000000000..a11b45516 --- /dev/null +++ b/crates/sargon/src/subsystems/log_system/mod.rs @@ -0,0 +1,3 @@ +mod log_system; + +pub use log_system::*; diff --git a/crates/sargon/src/subsystems/mod.rs b/crates/sargon/src/subsystems/mod.rs new file mode 100644 index 000000000..a11b45516 --- /dev/null +++ b/crates/sargon/src/subsystems/mod.rs @@ -0,0 +1,3 @@ +mod log_system; + +pub use log_system::*; diff --git a/crates/sargon/uniffi.toml b/crates/sargon/uniffi.toml new file mode 100644 index 000000000..0609f0cbe --- /dev/null +++ b/crates/sargon/uniffi.toml @@ -0,0 +1,8 @@ +namespace = "sargon" + +[bindings.swift] +module_name = "SargonOS" +experimental_sendable_value_types = true + +[bindings.kotlin] +package_name = "com.radixdlt.sargon.os" diff --git a/crates/transaction/Cargo.toml b/crates/transaction/Cargo.toml new file mode 100644 index 000000000..4be9ac594 --- /dev/null +++ b/crates/transaction/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "transaction" +version = "1.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } +serde = { workspace = true } +serde_json = { workspace = true } +log = { workspace = true } +rand = { workspace = true } +pretty_env_logger = { workspace = true } +derive_more = { workspace = true } +pretty_assertions = { workspace = true } +sargoncommon = { path = "../common" } +ret = { path = "../ret" } +profile = { path = "../profile" } + +sbor = { workspace = true } +radix-rust = { workspace = true } +radix-engine = { workspace = true } +radix-common = { workspace = true } +radix-engine-interface = { workspace = true } +radix-engine-toolkit = { workspace = true } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +# cargo_toml = "0.15.3" +cargo_toml = { git = "https://gitlab.com/lib.rs/cargo_toml", rev = "e498c94fc42a660c1ca1a28999ce1d757cfe77fe" } + + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } + +[lib] +crate-type = ["staticlib", "cdylib", "lib"] diff --git a/build.rs b/crates/transaction/build.rs similarity index 86% rename from build.rs rename to crates/transaction/build.rs index 0a46f39c6..cf8cd5e44 100644 --- a/build.rs +++ b/crates/transaction/build.rs @@ -1,5 +1,4 @@ use cargo_toml::{Dependency, Manifest}; -use std::env; use std::path::Path; pub fn main() { @@ -7,30 +6,30 @@ pub fn main() { Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml"); // Paths for reading fixtures used by tests - let fixtures_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("fixtures"); + let fixtures_path = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../fixtures"); println!("cargo:rustc-env=FIXTURES={}/", fixtures_path.display()); + let fixtures_transaction_path = fixtures_path.join("transaction"); println!( "cargo:rustc-env=FIXTURES_TX={}/", fixtures_transaction_path.display() ); + let fixtures_vector_path = fixtures_path.join("vector"); println!( "cargo:rustc-env=FIXTURES_VECTOR={}/", fixtures_vector_path.display() ); + let fixtures_models_path = fixtures_path.join("models"); println!( "cargo:rustc-env=FIXTURES_MODELS={}/", fixtures_models_path.display() ); - let fixtures_gw_models_path = fixtures_models_path.join("gateway"); - println!( - "cargo:rustc-env=FIXTURES_MODELS_GW={}/", - fixtures_gw_models_path.display() - ); let manifest = Manifest::from_path(manifest_path).expect("Can't panic"); + let dependencies = manifest.dependencies; let set_dep_env = |key: &str| { let dependency = dependencies.get(key).expect("Can't panic"); @@ -61,6 +60,6 @@ pub fn main() { set_dep_env("radix-engine"); set_dep_env("radix-engine-toolkit"); - uniffi::generate_scaffolding("src/sargon.udl") + uniffi::generate_scaffolding("src/transaction.udl") .expect("Should be able to build."); } diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/bucket.rs b/crates/transaction/src/high_level/manifest_building/bucket.rs similarity index 90% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/bucket.rs rename to crates/transaction/src/high_level/manifest_building/bucket.rs index 72721ac8e..14aec1ef7 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/bucket.rs +++ b/crates/transaction/src/high_level/manifest_building/bucket.rs @@ -1,8 +1,8 @@ use crate::prelude::*; #[derive(Clone)] -pub(crate) struct Bucket { - pub(crate) name: String, +pub struct Bucket { + pub name: String, } impl AsRef for Bucket { diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/bucket_factory.rs b/crates/transaction/src/high_level/manifest_building/bucket_factory.rs similarity index 89% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/bucket_factory.rs rename to crates/transaction/src/high_level/manifest_building/bucket_factory.rs index aa1de65a7..64dae8e9b 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/bucket_factory.rs +++ b/crates/transaction/src/high_level/manifest_building/bucket_factory.rs @@ -1,11 +1,11 @@ use crate::prelude::*; #[derive(Default)] -pub(crate) struct BucketFactory { +pub struct BucketFactory { next_id: std::cell::Cell, } impl BucketFactory { - pub(crate) fn next(&self) -> Bucket { + pub fn next(&self) -> Bucket { let next = self.next_id.get(); let bucket = Bucket { name: format!("bucket_{}", next), diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifest_assets_transfers.rs b/crates/transaction/src/high_level/manifest_building/manifest_assets_transfers.rs similarity index 97% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifest_assets_transfers.rs rename to crates/transaction/src/high_level/manifest_building/manifest_assets_transfers.rs index fb22a4cf1..4966e3e55 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifest_assets_transfers.rs +++ b/crates/transaction/src/high_level/manifest_building/manifest_assets_transfers.rs @@ -1,17 +1,19 @@ use crate::prelude::*; -impl TransactionManifest { +pub trait TransfersBuilding: Sized { + fn per_asset_transfers(transfers: PerAssetTransfers) -> Self; + /// Uses `per_asset_transfers` after having transposed the `PerRecipientAssetTransfers` /// into `PerAssetTransfers`. We always use `PerAssetTransfers` when building the manifest /// since it is more efficient (allows a single withdraw per resource) => fewer instruction => /// cheaper TX fee for user. - pub fn per_recipient_transfers( - transfers: PerRecipientAssetTransfers, - ) -> Self { + fn per_recipient_transfers(transfers: PerRecipientAssetTransfers) -> Self { Self::per_asset_transfers(transfers.transpose()) } +} - pub fn per_asset_transfers(transfers: PerAssetTransfers) -> Self { +impl TransfersBuilding for TransactionManifest { + fn per_asset_transfers(transfers: PerAssetTransfers) -> Self { let mut builder = ScryptoManifestBuilder::new(); let bucket_factory = BucketFactory::default(); let from_account = &transfers.from_account; diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests.rs b/crates/transaction/src/high_level/manifest_building/manifests.rs similarity index 89% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests.rs rename to crates/transaction/src/high_level/manifest_building/manifests.rs index 8b4afbe90..764171da4 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests.rs +++ b/crates/transaction/src/high_level/manifest_building/manifests.rs @@ -1,7 +1,14 @@ use crate::prelude::*; -impl TransactionManifest { - pub fn faucet( +pub trait FaucetManifestBuilding { + fn faucet( + include_lock_fee_instruction: bool, + address_of_receiving_account: &AccountAddress, + ) -> Self; +} + +impl FaucetManifestBuilding for TransactionManifest { + fn faucet( include_lock_fee_instruction: bool, address_of_receiving_account: &AccountAddress, ) -> Self { @@ -23,8 +30,18 @@ impl TransactionManifest { address_of_receiving_account.network_id(), ) } +} + +pub trait MetadataManifestBuilding: Sized { + fn set_metadata( + address: &A, + key: MetadataKey, + value: impl ScryptoToMetadataEntry, + ) -> Self + where + A: IntoScryptoAddress; - pub fn marking_account_as_dapp_definition_type( + fn marking_account_as_dapp_definition_type( account_address: &AccountAddress, ) -> Self { Self::set_metadata( @@ -34,7 +51,7 @@ impl TransactionManifest { ) } - pub fn set_owner_keys_hashes( + fn set_owner_keys_hashes( address_of_account_or_persona: &AddressOfAccountOrPersona, owner_key_hashes: Vec, ) -> Self { @@ -46,7 +63,37 @@ impl TransactionManifest { ), ) } +} + +impl MetadataManifestBuilding for TransactionManifest { + fn set_metadata( + address: &A, + key: MetadataKey, + value: impl ScryptoToMetadataEntry, + ) -> Self + where + A: IntoScryptoAddress, + { + let builder = ScryptoManifestBuilder::new().set_metadata( + address.scrypto(), + key, + value, + ); + + TransactionManifest::sargon_built(builder, address.network_id()) + } +} +pub trait AccountWithdrawalManifestBuilding { + fn account_withdraw_non_fungibles( + builder: ScryptoManifestBuilder, + owner: &AccountAddress, + resource_address: &ResourceAddress, + non_fungible_local_ids: &[NonFungibleLocalId], + ) -> ScryptoManifestBuilder; +} + +impl AccountWithdrawalManifestBuilding for TransactionManifest { fn account_withdraw_non_fungibles( builder: ScryptoManifestBuilder, owner: &AccountAddress, @@ -62,8 +109,17 @@ impl TransactionManifest { .map(ScryptoNonFungibleLocalId::from), ) } +} - pub fn stake_claims( +pub trait StakeClaimManifestBuilding { + fn stake_claims( + owner: &AccountAddress, + stake_claims: Vec, + ) -> Self; +} + +impl StakeClaimManifestBuilding for TransactionManifest { + fn stake_claims( owner: &AccountAddress, stake_claims: Vec, ) -> Self { @@ -117,30 +173,10 @@ impl TransactionManifest { } } -impl TransactionManifest { - fn set_metadata( - address: &A, - key: MetadataKey, - value: impl ScryptoToMetadataEntry, - ) -> Self - where - A: IntoScryptoAddress, - { - let builder = ScryptoManifestBuilder::new().set_metadata( - address.scrypto(), - key, - value, - ); - - TransactionManifest::sargon_built(builder, address.network_id()) - } -} - #[cfg(test)] mod tests { use super::*; use pretty_assertions::{assert_eq, assert_ne}; - use rand::Rng; #[allow(clippy::upper_case_acronyms)] type SUT = TransactionManifest; diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests_create_tokens.rs b/crates/transaction/src/high_level/manifest_building/manifests_create_tokens.rs similarity index 91% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests_create_tokens.rs rename to crates/transaction/src/high_level/manifest_building/manifests_create_tokens.rs index 509b5b8f3..75bd96ec1 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifests_create_tokens.rs +++ b/crates/transaction/src/high_level/manifest_building/manifests_create_tokens.rs @@ -1,37 +1,41 @@ use crate::prelude::*; -impl TransactionManifest { - pub fn create_fungible_token(address_of_owner: &AccountAddress) -> Self { - Self::create_fungible_token_with_metadata( - address_of_owner, - 21_000_000.into(), - TokenDefinitionMetadata::sample(), - ) - } +pub trait IsTokenCreating: Sized { + fn create_multiple_fungible_tokens( + address_of_owner: &AccountAddress, + count: impl Into>, + ) -> TransactionManifest; - pub fn create_fungible_token_with_metadata( + fn create_non_fungible_tokens( address_of_owner: &AccountAddress, + collection_count: u16, + initial_supply: T, + ) -> Self + where + T: Clone + IntoIterator, + V: ScryptoManifestEncode + ScryptoNonFungibleData; + + fn create_fungible_token_with_metadata_without_deposit( + builder: ScryptoManifestBuilder, initial_supply: Decimal192, metadata: TokenDefinitionMetadata, - ) -> Self { - let mut builder = ScryptoManifestBuilder::new(); - builder = Self::create_fungible_token_with_metadata_without_deposit( - builder, - initial_supply, - metadata, - ); - builder = builder.try_deposit_entire_worktop_or_abort( - address_of_owner.scrypto(), - None, - ); + ) -> ScryptoManifestBuilder; - TransactionManifest::sargon_built( - builder, - address_of_owner.network_id(), + fn create_fungible_token_with_metadata( + address_of_owner: &AccountAddress, + initial_supply: Decimal192, + metadata: TokenDefinitionMetadata, + ) -> Self; + + fn create_fungible_token(address_of_owner: &AccountAddress) -> Self { + Self::create_fungible_token_with_metadata( + address_of_owner, + 21_000_000.into(), + TokenDefinitionMetadata::sample(), ) } - pub fn create_single_nft_collection( + fn create_single_nft_collection( address_of_owner: &AccountAddress, nfts_per_collection: u64, ) -> Self { @@ -42,7 +46,7 @@ impl TransactionManifest { ) } - pub fn create_multiple_nft_collections( + fn create_multiple_nft_collections( address_of_owner: &AccountAddress, collection_count: u16, nfts_per_collection: u64, @@ -67,7 +71,6 @@ impl TransactionManifest { ) } - #[cfg(not(tarpaulin_include))] // false negative, tested fn create_non_fungible_tokens_collections_with_local_id_fn( address_of_owner: &AccountAddress, collection_count: u16, @@ -84,8 +87,31 @@ impl TransactionManifest { .map(|i| (local_id(i), NonFungibleTokenData::new(i))), ) } +} + +impl IsTokenCreating for TransactionManifest { + fn create_fungible_token_with_metadata( + address_of_owner: &AccountAddress, + initial_supply: Decimal192, + metadata: TokenDefinitionMetadata, + ) -> Self { + let mut builder = ScryptoManifestBuilder::new(); + builder = Self::create_fungible_token_with_metadata_without_deposit( + builder, + initial_supply, + metadata, + ); + builder = builder.try_deposit_entire_worktop_or_abort( + address_of_owner.scrypto(), + None, + ); + + TransactionManifest::sargon_built( + builder, + address_of_owner.network_id(), + ) + } - #[cfg(not(tarpaulin_include))] // false negative, tested fn create_non_fungible_tokens( address_of_owner: &AccountAddress, collection_count: u16, @@ -138,7 +164,7 @@ impl TransactionManifest { ) } - pub fn create_fungible_token_with_metadata_without_deposit( + fn create_fungible_token_with_metadata_without_deposit( builder: ScryptoManifestBuilder, initial_supply: Decimal192, metadata: TokenDefinitionMetadata, @@ -161,7 +187,7 @@ impl TransactionManifest { /// # Panics /// Panics if `address_of_owner` is on `Mainnet`, use a testnet instead. /// Panics if `count` is zero or is greater than the number of token metadata defined in `sample_resource_definition_metadata` (25) - pub fn create_multiple_fungible_tokens( + fn create_multiple_fungible_tokens( address_of_owner: &AccountAddress, count: impl Into>, ) -> TransactionManifest { @@ -182,7 +208,7 @@ impl TransactionManifest { let multiple_fungibles: MultipleFungibleTokens = serde_json::from_value(json).unwrap(); - info!("Generating multiple fungibles using bundled file, '\nDescription:\n'{}'", &multiple_fungibles.description); + debug!("Generating multiple fungibles using bundled file, '\nDescription:\n'{}'", &multiple_fungibles.description); let all_fungibles = multiple_fungibles.tokens; let max_count = all_fungibles.len(); let count = count.into().map(|c| c as usize).unwrap_or(max_count); @@ -218,7 +244,7 @@ impl TransactionManifest { } impl TokenDefinitionMetadata { - pub(crate) fn for_nft_collection(index: U11) -> Self { + pub fn for_nft_collection(index: U11) -> Self { let word = bip39_word_by_index(index.clone()).word; let name = capitalize(word.clone()); let base_url = "https://image-service-test-images.s3.eu-west-2.amazonaws.com/wallet_test_images/"; @@ -283,9 +309,7 @@ impl ScryptoNonFungibleData for NonFungibleTokenData { #[cfg(test)] mod tests { use crate::prelude::*; - use pretty_assertions::{assert_eq, assert_ne}; - use rand::Rng; - + use pretty_assertions::assert_eq; #[allow(clippy::upper_case_acronyms)] type SUT = TransactionManifest; diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/mod.rs b/crates/transaction/src/high_level/manifest_building/mod.rs similarity index 68% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/mod.rs rename to crates/transaction/src/high_level/manifest_building/mod.rs index acdc30d6c..43861a685 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/mod.rs +++ b/crates/transaction/src/high_level/manifest_building/mod.rs @@ -1,21 +1,15 @@ -mod addresses_manifest_builder_support; -mod assert_manifest; mod bucket; mod bucket_factory; mod manifest_assets_transfers; mod manifests; mod manifests_create_tokens; -mod metadata; mod modify_manifest; mod third_party_deposit_update; -pub use addresses_manifest_builder_support::*; -pub use assert_manifest::*; pub use bucket::*; pub use bucket_factory::*; pub use manifest_assets_transfers::*; pub use manifests::*; pub use manifests_create_tokens::*; -pub use metadata::*; pub use modify_manifest::*; pub use third_party_deposit_update::*; diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/modify_manifest.rs b/crates/transaction/src/high_level/manifest_building/modify_manifest.rs similarity index 97% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/modify_manifest.rs rename to crates/transaction/src/high_level/manifest_building/modify_manifest.rs index c57775ec8..a0b2fe8f8 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/modify_manifest.rs +++ b/crates/transaction/src/high_level/manifest_building/modify_manifest.rs @@ -56,7 +56,14 @@ where instruction[0].clone() } -impl TransactionManifest { +pub trait InstructionInserting: Sized { + fn instructions(&self) -> &Vec; + fn insert_instruction( + self, + position: InstructionPosition, + instruction: ScryptoInstruction, + ) -> Self; + /// Modifies `manifest` by inserting transaction "guarantees", which is the wallet /// term for `assert_worktop_contains`. /// @@ -66,7 +73,7 @@ impl TransactionManifest { /// /// Also panics if the number of TransactionGuarantee's is larger than the number /// of instructions of `manifest` (does not make any sense). - pub(crate) fn modify_add_guarantees(self, guarantees: I) -> Self + fn modify_add_guarantees(self, guarantees: I) -> Self where I: IntoIterator, { @@ -121,7 +128,7 @@ impl TransactionManifest { manifest } - pub(crate) fn modify_add_lock_fee( + fn modify_add_lock_fee( self, address_of_fee_payer: &AccountAddress, fee: Option, @@ -134,7 +141,12 @@ impl TransactionManifest { fn prepend_instruction(self, instruction: ScryptoInstruction) -> Self { self.insert_instruction(InstructionPosition::First, instruction) } +} +impl InstructionInserting for TransactionManifest { + fn instructions(&self) -> &Vec { + self.instructions() + } fn insert_instruction( self, position: InstructionPosition, @@ -161,14 +173,13 @@ impl TransactionManifest { } } -enum InstructionPosition { +pub enum InstructionPosition { First, At(u64), } #[cfg(test)] mod tests { - use radix_engine_interface::blueprints::account::AccountLockFeeInput; use super::*; diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/sample_resource_definition_metadata.json b/crates/transaction/src/high_level/manifest_building/sample_resource_definition_metadata.json similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/sample_resource_definition_metadata.json rename to crates/transaction/src/high_level/manifest_building/sample_resource_definition_metadata.json diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/third_party_deposit_update/manifest_third_party_deposit_update.rs b/crates/transaction/src/high_level/manifest_building/third_party_deposit_update/manifest_third_party_deposit_update.rs similarity index 93% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/third_party_deposit_update/manifest_third_party_deposit_update.rs rename to crates/transaction/src/high_level/manifest_building/third_party_deposit_update/manifest_third_party_deposit_update.rs index 251a5de9f..16439ed71 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/third_party_deposit_update/manifest_third_party_deposit_update.rs +++ b/crates/transaction/src/high_level/manifest_building/third_party_deposit_update/manifest_third_party_deposit_update.rs @@ -7,8 +7,13 @@ use radix_engine_interface::blueprints::account::{ ACCOUNT_SET_RESOURCE_PREFERENCE_IDENT, }; -impl TransactionManifest { - pub fn third_party_deposit_update( +pub trait IsThirdPartyDepositsUpdating: Sized { + fn third_party_deposit_update_by_delta( + owner: &AccountAddress, + delta: ThirdPartyDepositsDelta, + ) -> Self; + + fn third_party_deposit_update( owner: &AccountAddress, from: ThirdPartyDeposits, to: ThirdPartyDeposits, @@ -16,8 +21,10 @@ impl TransactionManifest { let delta = ThirdPartyDepositsDelta::new(from, to); Self::third_party_deposit_update_by_delta(owner, delta) } +} - pub fn third_party_deposit_update_by_delta( +impl IsThirdPartyDepositsUpdating for TransactionManifest { + fn third_party_deposit_update_by_delta( owner: &AccountAddress, delta: ThirdPartyDepositsDelta, ) -> Self { diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/third_party_deposit_update/mod.rs b/crates/transaction/src/high_level/manifest_building/third_party_deposit_update/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/third_party_deposit_update/mod.rs rename to crates/transaction/src/high_level/manifest_building/third_party_deposit_update/mod.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/third_party_deposit_update/third_party_deposits_delta.rs b/crates/transaction/src/high_level/manifest_building/third_party_deposit_update/third_party_deposits_delta.rs similarity index 88% rename from src/wrapped_radix_engine_toolkit/high_level/manifest_building/third_party_deposit_update/third_party_deposits_delta.rs rename to crates/transaction/src/high_level/manifest_building/third_party_deposit_update/third_party_deposits_delta.rs index a25420101..e140e8108 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/third_party_deposit_update/third_party_deposits_delta.rs +++ b/crates/transaction/src/high_level/manifest_building/third_party_deposit_update/third_party_deposits_delta.rs @@ -8,13 +8,13 @@ use radix_engine_interface::blueprints::account::{ #[derive(Debug, PartialEq, Eq, Default)] pub struct ThirdPartyDepositsDelta { - pub(crate) deposit_rule: Option, - pub(crate) asset_exceptions_to_be_removed: Vec, - pub(crate) asset_exceptions_to_add_or_update: + pub deposit_rule: Option, + pub asset_exceptions_to_be_removed: Vec, + pub asset_exceptions_to_add_or_update: Vec, - pub(crate) depositor_addresses_to_remove: + pub depositor_addresses_to_remove: Vec, - pub(crate) depositor_addresses_to_add: + pub depositor_addresses_to_add: Vec, } @@ -90,56 +90,6 @@ impl ThirdPartyDepositsDelta { } } -impl From for ScryptoAccountAddAuthorizedDepositorInput { - fn from(value: ResourceOrNonFungible) -> Self { - ScryptoAccountAddAuthorizedDepositorInput { - badge: value.into(), - } - } -} -impl From - for ScryptoAccountRemoveResourcePreferenceInput -{ - fn from(value: ResourceOrNonFungible) -> Self { - match value { - ResourceOrNonFungible::Resource { value } => Self { - resource_address: value.into(), - }, - ResourceOrNonFungible::NonFungible { value } => Self { - resource_address: value.resource_address.into(), - }, - } - } -} - -impl From for ScryptoAccountSetResourcePreferenceInput { - fn from(value: AssetException) -> Self { - Self { - resource_address: value.address.into(), - resource_preference: value.exception_rule.into(), - } - } -} - -impl From for ScryptoManifestValue { - fn from(value: AssetException) -> Self { - ScryptoManifestValue::from(value.address) - } -} - -impl From for ScryptoResourcePreference { - fn from(value: DepositAddressExceptionRule) -> Self { - match value { - DepositAddressExceptionRule::Allow => { - ScryptoResourcePreference::Allowed - } - DepositAddressExceptionRule::Deny => { - ScryptoResourcePreference::Disallowed - } - } - } -} - impl HasSampleValues for ThirdPartyDepositsDelta { fn sample() -> Self { Self::new( diff --git a/src/wrapped_radix_engine_toolkit/high_level/mod.rs b/crates/transaction/src/high_level/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/mod.rs rename to crates/transaction/src/high_level/mod.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/ret_api.rs b/crates/transaction/src/high_level/ret_api.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/ret_api.rs rename to crates/transaction/src/high_level/ret_api.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/account_or_address_of.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/account_or_address_of.rs similarity index 93% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/account_or_address_of.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/account_or_address_of.rs index a0e23fd5c..22e68b316 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/account_or_address_of.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/account_or_address_of.rs @@ -29,25 +29,25 @@ impl AccountOrAddressOf { } impl AccountOrAddressOf { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::ProfileAccount { value: Account::sample_mainnet_bob(), } } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::AddressOfExternalAccount { value: AccountAddress::sample_mainnet_other(), } } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::ProfileAccount { value: Account::sample_stokenet_nadia(), } } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::AddressOfExternalAccount { value: AccountAddress::sample_stokenet_other(), } diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/account_or_address_of_uniffi_fn.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/account_or_address_of_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/account_or_address_of_uniffi_fn.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/account_or_address_of_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/mod.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/mod.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/mod.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/mod.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/mod.rs similarity index 85% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/mod.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/mod.rs index 72fb76323..3eaac0feb 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/mod.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/mod.rs @@ -7,8 +7,7 @@ mod per_asset_transfers_of_non_fungible_resource; mod per_asset_transfers_uniffi_fn; pub use per_asset_fungible_resource::*; -pub use per_asset_fungible_transfer::*; -pub use per_asset_non_fungible_transfer::*; + pub use per_asset_transfers::*; pub use per_asset_transfers_of_fungible_resource::*; pub use per_asset_transfers_of_non_fungible_resource::*; diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_resource.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_resource.rs similarity index 90% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_resource.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_resource.rs index c7b38afff..c766ba196 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_resource.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_resource.rs @@ -19,19 +19,19 @@ impl PerAssetFungibleResource { } impl PerAssetFungibleResource { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::new(ResourceAddress::sample_mainnet_xrd(), None) } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::new(ResourceAddress::sample_mainnet_candy(), 4) } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::new(ResourceAddress::sample_stokenet_xrd(), None) } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::new(ResourceAddress::sample_stokenet_gum(), 6) } } diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_transfer.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_transfer.rs similarity index 94% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_transfer.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_transfer.rs index a5d9d7be8..582506356 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_transfer.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_transfer.rs @@ -47,7 +47,7 @@ impl HasSampleValues for PerAssetFungibleTransfer { } impl PerAssetFungibleTransfer { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::new( AccountOrAddressOf::ProfileAccount { value: Account::sample_mainnet_carol(), @@ -57,7 +57,7 @@ impl PerAssetFungibleTransfer { ) } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::new(AccountOrAddressOf::AddressOfExternalAccount { value: AccountAddress::from_str("account_rdx129a9wuey40lducsf6yu232zmzk5kscpvnl6fv472r0ja39f3hced69").unwrap() }, @@ -65,7 +65,7 @@ impl PerAssetFungibleTransfer { Decimal192::from_str("987654.1234").unwrap()) } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::new( AccountOrAddressOf::ProfileAccount { value: Account::sample_stokenet_olivia(), @@ -75,7 +75,7 @@ impl PerAssetFungibleTransfer { ) } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::new(AccountOrAddressOf::AddressOfExternalAccount { value: AccountAddress::from_str("account_tdx_2_1288efhmjt8kzce77par4ex997x2zgnlv5qqv9ltpxqg7ur0xpqm6gk").unwrap() }, diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_non_fungible_transfer.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_non_fungible_transfer.rs similarity index 94% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_non_fungible_transfer.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_non_fungible_transfer.rs index 98e6a7439..961413b92 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_non_fungible_transfer.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_non_fungible_transfer.rs @@ -40,7 +40,7 @@ impl From<(&AccountOrAddressOf, PerRecipientNonFungiblesTransfer)> } impl PerAssetNonFungibleTransfer { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::new( AccountOrAddressOf::ProfileAccount { value: Account::sample_mainnet_carol(), @@ -53,7 +53,7 @@ impl PerAssetNonFungibleTransfer { ) } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::new( AccountOrAddressOf::AddressOfExternalAccount { value: AccountAddress::from_str("account_rdx129a9wuey40lducsf6yu232zmzk5kscpvnl6fv472r0ja39f3hced69").unwrap() @@ -63,7 +63,7 @@ impl PerAssetNonFungibleTransfer { ) } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::new( AccountOrAddressOf::ProfileAccount { value: Account::sample_stokenet_nadia(), @@ -76,7 +76,7 @@ impl PerAssetNonFungibleTransfer { ) } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::new( AccountOrAddressOf::AddressOfExternalAccount { value: AccountAddress::from_str("account_tdx_2_1288efhmjt8kzce77par4ex997x2zgnlv5qqv9ltpxqg7ur0xpqm6gk").unwrap() diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_fungible_resource.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_fungible_resource.rs similarity index 90% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_fungible_resource.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_fungible_resource.rs index 67238840a..0c3a48dba 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_fungible_resource.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_fungible_resource.rs @@ -7,10 +7,7 @@ pub struct PerAssetTransfersOfFungibleResource { } impl PerAssetTransfersOfFungibleResource { - pub(crate) fn expanded( - &mut self, - transfer: impl Into, - ) { + pub fn expanded(&mut self, transfer: impl Into) { self.transfers.push(transfer.into()); } } @@ -42,7 +39,7 @@ impl PerAssetTransfersOfFungibleResource { } impl PerAssetTransfersOfFungibleResource { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::new( PerAssetFungibleResource::sample_mainnet(), [ @@ -52,14 +49,14 @@ impl PerAssetTransfersOfFungibleResource { ) } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::new( PerAssetFungibleResource::sample_mainnet_other(), [PerAssetFungibleTransfer::sample_mainnet_other()], ) } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::new( PerAssetFungibleResource::sample_stokenet(), [ @@ -69,7 +66,7 @@ impl PerAssetTransfersOfFungibleResource { ) } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::new( PerAssetFungibleResource::sample_stokenet_other(), [PerAssetFungibleTransfer::sample_stokenet_other()], diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_non_fungible_resource.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_non_fungible_resource.rs similarity index 93% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_non_fungible_resource.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_non_fungible_resource.rs index f877bfc63..50b236d40 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_non_fungible_resource.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_non_fungible_resource.rs @@ -19,7 +19,7 @@ impl PerAssetTransfersOfNonFungibleResource { } impl PerAssetTransfersOfNonFungibleResource { - pub(crate) fn expanded( + pub fn expanded( &mut self, transfer: impl Into, ) { @@ -39,7 +39,7 @@ impl PerAssetTransfersOfNonFungibleResource { } impl PerAssetTransfersOfNonFungibleResource { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::new( NonFungibleResourceAddress::sample_mainnet(), [ @@ -49,14 +49,14 @@ impl PerAssetTransfersOfNonFungibleResource { ) } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::new( NonFungibleResourceAddress::sample_mainnet_other(), [PerAssetNonFungibleTransfer::sample_mainnet_other()], ) } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::new( NonFungibleResourceAddress::sample_stokenet(), [ @@ -66,7 +66,7 @@ impl PerAssetTransfersOfNonFungibleResource { ) } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::new( NonFungibleResourceAddress::sample_stokenet_other(), [PerAssetNonFungibleTransfer::sample_stokenet_other()], diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_uniffi_fn.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_uniffi_fn.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/mod.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/mod.rs similarity index 71% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/mod.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/mod.rs index 9927c2f43..c89be841b 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/mod.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/mod.rs @@ -5,5 +5,3 @@ mod per_recipient_non_fungibles_transfer; pub use per_recipient_asset_transfer::*; pub use per_recipient_asset_transfers::*; -pub use per_recipient_fungible_transfer::*; -pub use per_recipient_non_fungibles_transfer::*; diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfer.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfer.rs similarity index 94% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfer.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfer.rs index 187f987a0..a7702396c 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfer.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfer.rs @@ -23,7 +23,7 @@ impl PerRecipientAssetTransfer { #[allow(unused)] impl PerRecipientAssetTransfer { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::new( AccountOrAddressOf::sample_mainnet(), [ @@ -37,7 +37,7 @@ impl PerRecipientAssetTransfer { ) } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::new( AccountOrAddressOf::sample_mainnet_other(), [PerRecipientFungibleTransfer::sample_mainnet_other()], @@ -45,7 +45,7 @@ impl PerRecipientAssetTransfer { ) } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::new( AccountOrAddressOf::sample_stokenet(), [ @@ -59,7 +59,7 @@ impl PerRecipientAssetTransfer { ) } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::new( AccountOrAddressOf::sample_stokenet_other(), [PerRecipientFungibleTransfer::sample_stokenet_other()], diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfers.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfers.rs similarity index 98% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfers.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfers.rs index 59c6d31dc..49827635e 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfers.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfers.rs @@ -21,7 +21,7 @@ impl PerRecipientAssetTransfers { #[allow(unused)] impl PerRecipientAssetTransfers { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::new( AccountAddress::sample_mainnet(), [ @@ -31,14 +31,14 @@ impl PerRecipientAssetTransfers { ) } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::new( AccountAddress::sample_mainnet_other(), [PerRecipientAssetTransfer::sample_mainnet_other()], ) } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::new( AccountAddress::sample_stokenet(), [ @@ -48,7 +48,7 @@ impl PerRecipientAssetTransfers { ) } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::new( AccountAddress::sample_stokenet_other(), [PerRecipientAssetTransfer::sample_stokenet_other()], diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_fungible_transfer.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_fungible_transfer.rs similarity index 90% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_fungible_transfer.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_fungible_transfer.rs index 14278a378..420ff3d52 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_fungible_transfer.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_fungible_transfer.rs @@ -17,19 +17,19 @@ impl PerRecipientFungibleTransfer { } impl PerRecipientFungibleTransfer { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::new(ResourceAddress::sample_mainnet_xrd(), 237, true, None) } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::new(ResourceAddress::sample_mainnet_candy(), 1337, true, 4) } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::new(ResourceAddress::sample_stokenet_xrd(), 42, false, None) } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::new(ResourceAddress::sample_stokenet_candy(), 3, true, 6) } } diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_non_fungibles_transfer.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_non_fungibles_transfer.rs similarity index 92% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_non_fungibles_transfer.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_non_fungibles_transfer.rs index 3f6617008..f9fa10f2d 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_non_fungibles_transfer.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_non_fungibles_transfer.rs @@ -15,7 +15,7 @@ impl PerRecipientNonFungiblesTransfer { } impl PerRecipientNonFungiblesTransfer { - pub(crate) fn sample_mainnet() -> Self { + pub fn sample_mainnet() -> Self { Self::new( ResourceAddress::sample_mainnet_xrd(), true, @@ -26,7 +26,7 @@ impl PerRecipientNonFungiblesTransfer { ) } - pub(crate) fn sample_mainnet_other() -> Self { + pub fn sample_mainnet_other() -> Self { Self::new( ResourceAddress::sample_mainnet_candy(), true, @@ -37,7 +37,7 @@ impl PerRecipientNonFungiblesTransfer { ) } - pub(crate) fn sample_stokenet() -> Self { + pub fn sample_stokenet() -> Self { Self::new( ResourceAddress::sample_stokenet_xrd(), false, @@ -48,7 +48,7 @@ impl PerRecipientNonFungiblesTransfer { ) } - pub(crate) fn sample_stokenet_other() -> Self { + pub fn sample_stokenet_other() -> Self { Self::new( ResourceAddress::sample_stokenet_candy(), true, diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/transfer_types.rs b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/transfer_types.rs similarity index 89% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/transfer_types.rs rename to crates/transaction/src/high_level/sargon_specific_types/assets_transfers/transfer_types.rs index 4c8d1ebf4..e80fd5c27 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/transfer_types.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/assets_transfers/transfer_types.rs @@ -21,7 +21,7 @@ macro_rules! decl_transfer_of { /// (`AccountOrAddressOf::ProfileAccount`) controlled by a DeviceFactorSource thy have /// access to and which third party deposit setting's `DepositRule` is `AcceptKnown` and /// which resource is known (`resource_address` is owned or has been owned before). - pub(crate) use_try_deposit_or_abort: bool, + pub use_try_deposit_or_abort: bool, $($fields)* @@ -53,7 +53,7 @@ macro_rules! decl_per_asset_transfer_of { paste! { impl [< PerAsset $struct_name Transfer>] { - pub(crate) fn deposit_instruction(&self, builder: ScryptoManifestBuilder, bucket: &Bucket) -> ScryptoManifestBuilder { + pub fn deposit_instruction(&self, builder: ScryptoManifestBuilder, bucket: &Bucket) -> ScryptoManifestBuilder { if self.use_try_deposit_or_abort { return builder.try_deposit_or_abort( @@ -97,14 +97,14 @@ decl_per_asset_transfer_of!( /// A fungible transfer to `recipient`, with a specified amount of tokens to send. Fungible, /// Amount - pub(crate) amount: Decimal192, + pub amount: Decimal192, ); decl_per_asset_transfer_of!( /// A non fungible transfer to `recipient`, with specified Local IDs to send. NonFungible, /// Amount - pub(crate) non_fungible_local_ids: Vec, + pub non_fungible_local_ids: Vec, ); decl_per_recipient_transfer_of!( @@ -112,7 +112,7 @@ decl_per_recipient_transfer_of!( /// of tokens and divisibility. Fungible, /// Amount - pub(crate) amount: Decimal192, + pub amount: Decimal192, pub divisibility: Option, ); @@ -120,5 +120,5 @@ decl_per_recipient_transfer_of!( /// A non fungible transfer of `resource_address` token, with specified Local IDs to send. NonFungibles, /// The local IDS of the NonFungible tokens being sent - pub(crate) local_ids: Vec, + pub local_ids: Vec, ); diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/dependency_information.rs b/crates/transaction/src/high_level/sargon_specific_types/build_information/dependency_information.rs similarity index 96% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/dependency_information.rs rename to crates/transaction/src/high_level/sargon_specific_types/build_information/dependency_information.rs index ec3e219c3..d82a21c44 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/dependency_information.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/build_information/dependency_information.rs @@ -21,7 +21,7 @@ pub enum DependencyInformation { } impl DependencyInformation { - pub(crate) fn with_value(version: &str) -> Self { + pub fn with_value(version: &str) -> Self { let mut split = version.split('='); let identifier = split.next().expect("Should never fail").trim(); let value = split.next().expect("Should never fail").trim(); diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/dependency_information_uniffi_fn.rs b/crates/transaction/src/high_level/sargon_specific_types/build_information/dependency_information_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/dependency_information_uniffi_fn.rs rename to crates/transaction/src/high_level/sargon_specific_types/build_information/dependency_information_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/mod.rs b/crates/transaction/src/high_level/sargon_specific_types/build_information/mod.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/mod.rs rename to crates/transaction/src/high_level/sargon_specific_types/build_information/mod.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/sargon_build_information.rs b/crates/transaction/src/high_level/sargon_specific_types/build_information/sargon_build_information.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/sargon_build_information.rs rename to crates/transaction/src/high_level/sargon_specific_types/build_information/sargon_build_information.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/sargon_build_information_uniffi_fn.rs b/crates/transaction/src/high_level/sargon_specific_types/build_information/sargon_build_information_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/sargon_build_information_uniffi_fn.rs rename to crates/transaction/src/high_level/sargon_specific_types/build_information/sargon_build_information_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/sargon_dependencies.rs b/crates/transaction/src/high_level/sargon_specific_types/build_information/sargon_dependencies.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/build_information/sargon_dependencies.rs rename to crates/transaction/src/high_level/sargon_specific_types/build_information/sargon_dependencies.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/mod.rs b/crates/transaction/src/high_level/sargon_specific_types/mod.rs similarity index 66% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/mod.rs rename to crates/transaction/src/high_level/sargon_specific_types/mod.rs index 31ec497d6..1c24f222a 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/mod.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/mod.rs @@ -1,19 +1,12 @@ -#[macro_use] -mod address_union; - -mod address_of_account_or_persona; mod assets_transfers; mod build_information; mod stake_claim; mod stake_claim_uniffi_fn; mod transaction_guarantee; -pub use address_of_account_or_persona::*; pub use assets_transfers::*; pub use build_information::*; -pub use address_union::*; - pub use stake_claim::*; pub use stake_claim_uniffi_fn::*; pub use transaction_guarantee::*; diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/stake_claim.rs b/crates/transaction/src/high_level/sargon_specific_types/stake_claim.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/stake_claim.rs rename to crates/transaction/src/high_level/sargon_specific_types/stake_claim.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/stake_claim_uniffi_fn.rs b/crates/transaction/src/high_level/sargon_specific_types/stake_claim_uniffi_fn.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/stake_claim_uniffi_fn.rs rename to crates/transaction/src/high_level/sargon_specific_types/stake_claim_uniffi_fn.rs diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/transaction_guarantee.rs b/crates/transaction/src/high_level/sargon_specific_types/transaction_guarantee.rs similarity index 96% rename from src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/transaction_guarantee.rs rename to crates/transaction/src/high_level/sargon_specific_types/transaction_guarantee.rs index a0a1fdf5e..54768fd5e 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/transaction_guarantee.rs +++ b/crates/transaction/src/high_level/sargon_specific_types/transaction_guarantee.rs @@ -25,7 +25,7 @@ impl TransactionGuarantee { } impl TransactionGuarantee { - pub(crate) fn rounded_amount(&self) -> Decimal192 { + pub fn rounded_amount(&self) -> Decimal192 { self.amount.clone().round(self.resource_divisibility) } } diff --git a/src/wrapped_radix_engine_toolkit/high_level/token_definition_metadata.rs b/crates/transaction/src/high_level/token_definition_metadata.rs similarity index 100% rename from src/wrapped_radix_engine_toolkit/high_level/token_definition_metadata.rs rename to crates/transaction/src/high_level/token_definition_metadata.rs diff --git a/crates/transaction/src/lib.rs b/crates/transaction/src/lib.rs new file mode 100644 index 000000000..1634ac7b9 --- /dev/null +++ b/crates/transaction/src/lib.rs @@ -0,0 +1,12 @@ +mod high_level; + +pub mod prelude { + + pub use crate::high_level::*; + + pub use profile::prelude::*; +} + +pub use prelude::*; + +uniffi::include_scaffolding!("transaction"); diff --git a/crates/transaction/src/transaction.udl b/crates/transaction/src/transaction.udl new file mode 100644 index 000000000..f17236b6e --- /dev/null +++ b/crates/transaction/src/transaction.udl @@ -0,0 +1 @@ +namespace transaction {}; diff --git a/crates/transaction/uniffi.toml b/crates/transaction/uniffi.toml new file mode 100644 index 000000000..8a421b697 --- /dev/null +++ b/crates/transaction/uniffi.toml @@ -0,0 +1,8 @@ +namespace = "transaction" + +[bindings.swift] +module_name = "SargonTransaction" +experimental_sendable_value_types = true + +[bindings.kotlin] +package_name = "com.radixdlt.sargon.transaction" diff --git a/crates/uniffi-bindgen/Cargo.toml b/crates/uniffi-bindgen/Cargo.toml new file mode 100644 index 000000000..a4a66fc8f --- /dev/null +++ b/crates/uniffi-bindgen/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "uniffi-bindgen" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "sargon-bindgen" +path = "src/main.rs" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } + +# clap = "4.5.1" +clap = { git = "https://github.com/clap-rs/clap/", rev = "8a7a13a5618cfdc4ff328624a5266e7b4d88649a", default-features = false, features = [ + "std", + "derive", +] } + +# regex = "1.9.3" +regex = { git = "https://github.com/rust-lang/regex/", rev = "72f889ef3cca59ebac6a026f3646e8d92f056d88" } + + +# camino = "1.0.8" +camino = { git = "https://github.com/camino-rs/camino/", rev = "afa51b1b4c684b7e6698a6717ccda3affd0abd42" } + +thiserror = { workspace = true } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } diff --git a/src/bindgen/args.rs b/crates/uniffi-bindgen/src/args.rs similarity index 98% rename from src/bindgen/args.rs rename to crates/uniffi-bindgen/src/args.rs index c605304a3..e0146e069 100644 --- a/src/bindgen/args.rs +++ b/crates/uniffi-bindgen/src/args.rs @@ -88,7 +88,7 @@ enum Commands { }, } -pub(crate) fn get_args() -> (String, Vec) { +pub fn get_args() -> (String, Vec) { let cli = Cli::parse(); match cli.command { Commands::Generate { diff --git a/src/bindgen/bindgen_error.rs b/crates/uniffi-bindgen/src/bindgen_error.rs similarity index 100% rename from src/bindgen/bindgen_error.rs rename to crates/uniffi-bindgen/src/bindgen_error.rs diff --git a/crates/uniffi-bindgen/src/main.rs b/crates/uniffi-bindgen/src/main.rs new file mode 100644 index 000000000..9964f4058 --- /dev/null +++ b/crates/uniffi-bindgen/src/main.rs @@ -0,0 +1,14 @@ +// mod args; +// mod bindgen_error; +// mod post_process; +// mod post_process_kotlin; +// mod post_process_swift; + +// use crate::post_process::*; + +fn main() { + // println!("🔮 Running sargon-bindgen"); + uniffi::uniffi_bindgen_main(); + // post_process(); + // println!("🔮 Finished with sargon-bindgen ✅"); +} diff --git a/src/bindgen/post_process.rs b/crates/uniffi-bindgen/src/post_process.rs similarity index 98% rename from src/bindgen/post_process.rs rename to crates/uniffi-bindgen/src/post_process.rs index d41973cbb..567f7be51 100644 --- a/src/bindgen/post_process.rs +++ b/crates/uniffi-bindgen/src/post_process.rs @@ -54,7 +54,7 @@ fn kotlin_post_process(out_dir: &String) -> Result<(), BindgenError> { .and_then(|t| convert(kotlin_transform, t)) } -pub(crate) fn post_process() { +pub fn post_process() { println!("🔮 sargon-bindgen | post processing..."); let (out_dir, languages) = get_args(); diff --git a/src/bindgen/post_process_kotlin.rs b/crates/uniffi-bindgen/src/post_process_kotlin.rs similarity index 91% rename from src/bindgen/post_process_kotlin.rs rename to crates/uniffi-bindgen/src/post_process_kotlin.rs index 7b9d4a743..d0cbbcc1b 100644 --- a/src/bindgen/post_process_kotlin.rs +++ b/crates/uniffi-bindgen/src/post_process_kotlin.rs @@ -1,10 +1,8 @@ -extern crate sargon; use regex::Regex; -use sargon::prelude::*; use crate::bindgen_error::BindgenError; -pub(crate) fn kotlin_transform( +pub fn kotlin_transform( needle: &str, contents: String, ) -> Result { diff --git a/src/bindgen/post_process_swift.rs b/crates/uniffi-bindgen/src/post_process_swift.rs similarity index 96% rename from src/bindgen/post_process_swift.rs rename to crates/uniffi-bindgen/src/post_process_swift.rs index 38fc85801..eb7101ff0 100644 --- a/src/bindgen/post_process_swift.rs +++ b/crates/uniffi-bindgen/src/post_process_swift.rs @@ -1,6 +1,6 @@ use crate::bindgen_error::BindgenError; -pub(crate) fn swift_transform( +pub fn swift_transform( needle: &str, contents: String, ) -> Result { diff --git a/examples/android/src/main/java/com/radixdlt/sargon/android/EphemeralKeystore.kt b/examples/android/src/main/java/com/radixdlt/sargon/android/EphemeralKeystore.kt index abec03b6c..681b077f6 100644 --- a/examples/android/src/main/java/com/radixdlt/sargon/android/EphemeralKeystore.kt +++ b/examples/android/src/main/java/com/radixdlt/sargon/android/EphemeralKeystore.kt @@ -1,35 +1,36 @@ package com.radixdlt.sargon.android -import com.radixdlt.sargon.SecureStorage +import com.radixdlt.sargon.BagOfBytes +import com.radixdlt.sargon.SecureStorageDriver import com.radixdlt.sargon.SecureStorageKey import com.radixdlt.sargon.extensions.identifier -class EphemeralKeystore: SecureStorage { - private val storage: MutableMap = mutableMapOf() +class EphemeralKeystore: SecureStorageDriver { + private val storage: MutableMap = mutableMapOf() - override fun loadData(key: SecureStorageKey): ByteArray? = storage[key.identifier] + override suspend fun loadData(key: SecureStorageKey): BagOfBytes? = storage[key.identifier] - override fun saveData(key: SecureStorageKey, data: ByteArray) { + override suspend fun saveData(key: SecureStorageKey, data: BagOfBytes) { storage[key.identifier] = data } - override fun deleteDataForKey(key: SecureStorageKey) { + override suspend fun deleteDataForKey(key: SecureStorageKey) { storage.remove(key = key.identifier) } fun isEmpty() = storage.isEmpty() - fun contains(value: String): Boolean { - return storage.any { entry -> - entry.value.decodeToString().contains(value) - } - } - - override fun toString(): String { - return storage.toList().joinToString(prefix = "[", postfix = "\n]") { pair -> - "\n\t${pair.first} => ${pair.second.decodeToString()}" - } - } +// fun contains(value: String): Boolean { +// return storage.any { entry -> +// entry.value().decodeToString().contains(value) +// } +// } +// +// override fun toString(): String { +// return storage.toList().joinToString(prefix = "[", postfix = "\n]") { pair -> +// "\n\t${pair.first} => ${pair.second.decodeToString()}" +// } +// } override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/examples/android/src/main/java/com/radixdlt/sargon/android/MainActivity.kt b/examples/android/src/main/java/com/radixdlt/sargon/android/MainActivity.kt index 574d4cca5..274349018 100644 --- a/examples/android/src/main/java/com/radixdlt/sargon/android/MainActivity.kt +++ b/examples/android/src/main/java/com/radixdlt/sargon/android/MainActivity.kt @@ -36,7 +36,7 @@ import com.radixdlt.sargon.NetworkId import com.radixdlt.sargon.NonEmptyMax32Bytes import com.radixdlt.sargon.Profile import com.radixdlt.sargon.ProfileNetwork -import com.radixdlt.sargon.SecureStorage +import com.radixdlt.sargon.SecureStorageDriver import com.radixdlt.sargon.Wallet import com.radixdlt.sargon.WalletClientModel import com.radixdlt.sargon.android.ui.theme.SargonAndroidTheme @@ -57,7 +57,7 @@ class MainActivity : ComponentActivity() { } @Composable -fun WalletContent(modifier: Modifier = Modifier, storage: SecureStorage) { +fun WalletContent(modifier: Modifier = Modifier, storage: SecureStorageDriver) { var walletState: Wallet? by remember { mutableStateOf(null) } var profile: Profile? by remember { mutableStateOf(null) } @@ -173,7 +173,7 @@ val Wallet.Companion.defaultPhoneName: String fun Wallet.Companion.with( entropy: ByteArray = ByteArray(32).apply { Random.nextBytes(this) }, phoneName: String = Wallet.Companion.defaultPhoneName, - secureStorage: SecureStorage + secureStorage: SecureStorageDriver ): Wallet { return Wallet.byCreatingNewProfileAndSecretsWithEntropy( entropy = NonEmptyMax32Bytes(entropy.toBagOfBytes()), diff --git a/examples/iOS/App.xctestplan b/examples/iOS/App.xctestplan new file mode 100644 index 000000000..0cfa71512 --- /dev/null +++ b/examples/iOS/App.xctestplan @@ -0,0 +1,29 @@ +{ + "configurations" : [ + { + "id" : "61024E3D-4D07-4C71-9C15-82BE0C20962B", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false, + "targetForVariableExpansion" : { + "containerPath" : "container:PlanbokApp.xcodeproj", + "identifier" : "48F0A6C82B7D42D400929DC2", + "name" : "PlanbokApp" + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:..\/Backend", + "identifier" : "PlanbokTests", + "name" : "PlanbokTests" + } + } + ], + "version" : 1 +} diff --git a/examples/iOS/Backend/.swiftpm/xcode/xcshareddata/xcschemes/PlanbokTests.xcscheme b/examples/iOS/Backend/.swiftpm/xcode/xcshareddata/xcschemes/PlanbokTests.xcscheme new file mode 100644 index 000000000..b6468af4c --- /dev/null +++ b/examples/iOS/Backend/.swiftpm/xcode/xcshareddata/xcschemes/PlanbokTests.xcscheme @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/iOS/Backend/Package.swift b/examples/iOS/Backend/Package.swift index f7c23fd2f..291031f66 100644 --- a/examples/iOS/Backend/Package.swift +++ b/examples/iOS/Backend/Package.swift @@ -18,10 +18,10 @@ let package = Package( .package(name: "Sargon", path: "../../.."), .package( url: "https://github.com/pointfreeco/swift-composable-architecture", - from: "1.8.0"), + from: "1.10.4"), .package( url: "https://github.com/tgrapperon/swift-dependencies-additions", - from: "1.0.1"), + from: "1.0.2"), .package(url: "https://github.com/kishikawakatsumi/KeychainAccess", from: "4.2.2"), ], targets: [ diff --git a/examples/iOS/Backend/Sources/Planbok/Dependencies/AccountsClient.swift b/examples/iOS/Backend/Sources/Planbok/Dependencies/AccountsClient.swift new file mode 100644 index 000000000..5f1b32b6e --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Dependencies/AccountsClient.swift @@ -0,0 +1,75 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import Sargon +import DependenciesMacros +import IdentifiedCollections + +public typealias Accounts = IdentifiedArrayOf +extension Array where Element: Identifiable { + func asIdentified() -> IdentifiedArrayOf { + IdentifiedArrayOf.init(uncheckedUniqueElements: self) + } +} + +@DependencyClient +public struct AccountsClient: Sendable { + public typealias GetAccounts = @Sendable () -> Accounts + public typealias AccountsStream = @Sendable () -> AsyncStream + public typealias AccountByAddress = @Sendable (AccountAddress) throws -> Account + public typealias CreateAndSaveAccount = @Sendable (DisplayName) async throws -> Account + public typealias UpdateAccount = @Sendable (Account) async throws -> Void + public typealias BatchCreateManySavedAccounts = @Sendable (_ count: UInt16) async throws -> Void + + public var getAccounts: GetAccounts + public var accountsStream: AccountsStream + public var accountByAddress: AccountByAddress + public var createAndSaveAccount: CreateAndSaveAccount + public var updateAccount: UpdateAccount + public var batchCreateManySavedAccounts: BatchCreateManySavedAccounts +} + +extension AccountsClient: DependencyKey { + public static let liveValue = Self.live(os: SargonOS.shared) + public static func live(os: SargonOS) -> Self { + + let getAccounts: GetAccounts = { + os.accountsOnCurrentNetwork().asIdentified() + } + + return Self( + getAccounts: getAccounts, + accountsStream: { + AsyncStream { continuation in + Task { + for await _ in await EventBus.shared.notifications() { + continuation.yield(getAccounts()) + } + } + } + }, + accountByAddress: { address in + try os.accountByAddress(address: address) + }, + createAndSaveAccount: { + try await os.createAndSaveNewAccount(networkId: os.currentNetworkID, name: $0) + }, + updateAccount: { account in + log.debug("Updating account") + try await os.updateAccount(updated: account) + }, + batchCreateManySavedAccounts: { count in + try await os.batchCreateManyAccountsThenSaveOnce( + count: count, + networkId: os.currentNetworkID, + namePrefix: "Unnamed" + ) + } + ) + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Dependencies/GatewaysClient.swift b/examples/iOS/Backend/Sources/Planbok/Dependencies/GatewaysClient.swift new file mode 100644 index 000000000..f557d071e --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Dependencies/GatewaysClient.swift @@ -0,0 +1,29 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import Sargon +import DependenciesMacros + +@DependencyClient +public struct GatewaysClient: Sendable { + public typealias SwitchGatewayTo = @Sendable (Gateway) async throws -> Void + public var switchGatewayTo: SwitchGatewayTo +} + +extension GatewaysClient: DependencyKey { + public static let liveValue = Self.live(os: SargonOS.shared) + public static func live(os: SargonOS) -> Self { + Self( + switchGatewayTo: { to in + log.notice("Changing current gateway to: \(to)") + let _ = try await os.changeCurrentGateway(to: to) + log.info("Finished changing current gateway.") + } + ) + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Dependencies/Keychain.swift b/examples/iOS/Backend/Sources/Planbok/Dependencies/Keychain.swift index 5114d14ae..26d21a08e 100644 --- a/examples/iOS/Backend/Sources/Planbok/Dependencies/Keychain.swift +++ b/examples/iOS/Backend/Sources/Planbok/Dependencies/Keychain.swift @@ -1,56 +1,20 @@ // // File.swift -// +// // // Created by Alexander Cyon on 2024-02-15. // @_exported import KeychainAccess import Sargon -import SargonUniFFI +import ComposableArchitecture -extension DependencyValues { - /// A dependency that exposes an ``Keychain.Dependency`` value that you can use to read and - /// write to `Keychain`. - public var keychain: Keychain.Dependency { - get { self[Keychain.Dependency.self] } - set { self[Keychain.Dependency.self] = newValue } - } -} extension Keychain: @unchecked Sendable {} -extension Keychain: SecureStorage { - @DependencyClient - public struct Dependency: DependencyKey { - - public let loadData: @Sendable (SecureStorageKey) throws -> Data? - public let saveData: @Sendable (SecureStorageKey, Data) throws -> Void - public let deleteDataForKey: @Sendable (SecureStorageKey) throws -> Void - - public static func with(keychain: Keychain) -> Self { - Self.init( - loadData: keychain.loadData(key:), - saveData: keychain.saveData(key:data:), - deleteDataForKey: keychain.deleteDataForKey(key:) - ) - } - public static let liveValue = Self.with(keychain: .shared) - public static var testValue: Self { - final class Ephemeral { - var dict = Dictionary() - init() {} - } - let ephemeral = Ephemeral() - return Self( - loadData: { ephemeral.dict[$0] }, - saveData: { ephemeral.dict[$0] = $1 }, - deleteDataForKey: { ephemeral.dict[$0] = nil } - ) - } - } - +extension Keychain: SecureStorageDriver { + public static let shared = Keychain(service: "works.rdx.planbok") - + @Sendable public func loadData(key: SecureStorageKey) throws -> Data? { try getData(key.identifier) diff --git a/examples/iOS/Backend/Sources/Planbok/Dependencies/MnemonicClient.swift b/examples/iOS/Backend/Sources/Planbok/Dependencies/MnemonicClient.swift new file mode 100644 index 000000000..87003ef00 --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Dependencies/MnemonicClient.swift @@ -0,0 +1,27 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import Sargon +import DependenciesMacros + +@DependencyClient +public struct MnemonicClient: Sendable { + public typealias LoadMnemonic = @Sendable (FactorSourceIDFromHash) async throws -> PrivateHierarchicalDeterministicFactorSource + public var loadMnemonic: LoadMnemonic +} + +extension MnemonicClient: DependencyKey { + public static let liveValue = Self.live(os: SargonOS.shared) + public static func live(os: SargonOS) -> Self { + Self( + loadMnemonic: { id in + try await os.loadPrivateDeviceFactorSourceById(id: id) + } + ) + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Dependencies/ProfileClient.swift b/examples/iOS/Backend/Sources/Planbok/Dependencies/ProfileClient.swift new file mode 100644 index 000000000..40b118473 --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Dependencies/ProfileClient.swift @@ -0,0 +1,49 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import Sargon +import DependenciesMacros + +@DependencyClient +public struct ProfileClient: Sendable { + public typealias ActiveProfile = @Sendable () -> Profile + public typealias DeleteProfileAndMnemonicsThenCreateNew = @Sendable () async throws -> Void + public typealias ImportProfile = @Sendable (Profile) async throws -> Void + public typealias DecryptEncryptedProfile = @Sendable (_ encrypted: Data, _ password: String) throws -> Profile + + public typealias EmulateFreshInstallOfAppThenRestart = @Sendable () async throws -> Void + + public var activeProfile: ActiveProfile + public var deleteProfileAndMnemonicsThenCreateNew: DeleteProfileAndMnemonicsThenCreateNew + public var importProfile: ImportProfile + public var decryptEncryptedProfile: DecryptEncryptedProfile + public var emulateFreshInstallOfAppThenRestart: EmulateFreshInstallOfAppThenRestart +} + +extension ProfileClient: DependencyKey { + public static let liveValue = Self.live(os: SargonOS.shared) + public static func live(os: SargonOS) -> Self { + return Self( + activeProfile: { + os.profile() + }, + deleteProfileAndMnemonicsThenCreateNew: { + let _ = try await os.deleteProfileThenCreateNewWithBdfs() + }, + importProfile: { + try await os.importProfile(profile: $0) + }, + decryptEncryptedProfile: { + try Profile(encrypted: $0, decryptionPassword: $1) + }, + emulateFreshInstallOfAppThenRestart: { + try await os.emulateFreshInstall() + } + ) + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Dependencies/SharedState+PersistenceReaderKey+SargonKey.swift b/examples/iOS/Backend/Sources/Planbok/Dependencies/SharedState+PersistenceReaderKey+SargonKey.swift new file mode 100644 index 000000000..fab5941d0 --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Dependencies/SharedState+PersistenceReaderKey+SargonKey.swift @@ -0,0 +1,151 @@ +import Sargon +import Dependencies +import Foundation +import ComposableArchitecture + +public typealias AccountsForDisplay = IdentifiedArrayOf +extension IdentifiedArray where Element: Identifiable, Element.ID == ID { + public static var `default`: Self { IdentifiedArrayOf.init() } +} +extension NetworkID { + public static let `default` = Self.mainnet +} + +extension SargonOS { + + public var accountsForDisplayOnCurrentNetworkIdentified: AccountsForDisplay { + accountsForDisplayOnCurrentNetwork.asIdentified() + } + +} + +extension PersistenceReaderKey where Self == PersistenceKeyDefault> { + public static var accountsForDisplay: Self { + Self.sharedAccountsForDisplay + } +} + +extension PersistenceKeyDefault> { + public static let sharedAccountsForDisplay = Self( + SargonKey( + on: .currentAccounts, + accessing: \.accountsForDisplayOnCurrentNetworkIdentified + ), + .default + ) +} + +extension PersistenceReaderKey where Self == PersistenceKeyDefault> { + public static var network: Self { + Self.sharedNetwork + } +} + +extension PersistenceKeyDefault: @unchecked Sendable {} +extension PersistenceKeyDefault> { + public static let sharedNetwork = Self( + SargonKey( + on: .currentGateway, + accessing: \.currentNetworkID + ), + .default + ) +} + +extension PersistenceReaderKey where Self == PersistenceKeyDefault> { + public static var savedGateways: Self { + Self.sharedSavedGateways + } +} + +extension PersistenceKeyDefault> { + public static let sharedSavedGateways = Self( + SargonKey( + on: .currentGateway, + accessing: \.gateways + ), + .default + ) + +} + +extension Set { + public static let currentGateway: Self = [.booted, .importedProfile, .gatewayChangedCurrent] + public static let currentAccounts: Self = [.booted, .importedProfile, .addedAccount, .addedAccounts, .updatedAccount, .gatewayChangedCurrent] +} + +public struct SargonKey: Hashable, PersistenceReaderKey, Sendable { + public typealias LastValue = @Sendable () -> Value? + + private let lastValue: LastValue + private let fetchOnEvents: Set + + public init( + on fetchOnEvents: Set, + lastValueWithOS: @escaping @Sendable (SargonOS) -> Value? + ) { + self.fetchOnEvents = fetchOnEvents + self.lastValue = { + lastValueWithOS(SargonOS.shared) + } + } +} + +extension SargonKey { + public init( + on fetchOnEvents: Set, + accessing keyPath: KeyPath + ) { + self.init(on: fetchOnEvents, lastValueWithOS: { $0[keyPath: keyPath] }) + + log.warning("SharedState for \(String(describing: Value.self)), hopefully just one per value kind") + } +} + +extension AnyKeyPath: @unchecked Sendable {} + + +// MARK: PersistenceReaderKey +extension SargonKey { + public func load(initialValue: Value?) -> Value? { + lastValue() ?? initialValue + } + + public func subscribe( + initialValue: Value?, + didSet: @Sendable @escaping (_ newValue: Value?) -> Void + ) -> Shared.Subscription { + let task = Task { [fetchOnEvents = self.fetchOnEvents] in + for await _ in await EventBus.shared.notifications().map(\.event.kind).filter({ + fetchOnEvents.contains($0) + }) { + guard !Task.isCancelled else { return } + didSet(lastValue()) + } + } + return .init { + task.cancel() + } + } +} + +extension SargonKey { + private var valueKind: String { + String(describing: Value.self) + } +} + +// MARK: Equatable +extension SargonKey { + public static func == (lhs: SargonKey, rhs: SargonKey) -> Bool { + lhs.valueKind == rhs.valueKind && lhs.fetchOnEvents == rhs.fetchOnEvents + } +} + +// MARK: Hashable +extension SargonKey { + public func hash(into hasher: inout Hasher) { + hasher.combine(valueKind) + hasher.combine(fetchOnEvents) + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Dependencies/WalletHolder.swift b/examples/iOS/Backend/Sources/Planbok/Dependencies/WalletHolder.swift deleted file mode 100644 index 1ade2e828..000000000 --- a/examples/iOS/Backend/Sources/Planbok/Dependencies/WalletHolder.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// File.swift -// -// -// Created by Alexander Cyon on 2024-02-16. -// - -import Foundation -import Sargon -import SargonUniFFI - -public struct WalletHolder: Equatable, Sendable { - public static func == (lhs: Self, rhs: Self) -> Bool { - lhs.wallet === rhs.wallet - } - private var profile: Profile - public let wallet: Wallet - public init(wallet: Wallet) { - self.wallet = wallet - self.profile = wallet.profile() - } - - // FIXME: Replace this with an async stream of values... - public mutating func refresh() { - self.profile = wallet.profile() - } -} diff --git a/examples/iOS/Backend/Sources/Planbok/Features/AccountDetailsFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/AccountDetailsFeature.swift new file mode 100644 index 000000000..0341573d1 --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Features/AccountDetailsFeature.swift @@ -0,0 +1,94 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import Sargon +import ComposableArchitecture +import SwiftUI + +@Reducer +public struct AccountDetailsFeature { + + @Dependency(AccountsClient.self) var accountsClient + + @Reducer(state: .equatable) + public enum Destination { + case changeGradient(SelectGradientFeature) + } + + @ObservableState + public struct State: Equatable { + public var accountForDisplay: AccountForDisplay + + @Presents var destination: Destination.State? + + public init(accountForDisplay: AccountForDisplay) { + self.accountForDisplay = accountForDisplay + } + } + + public enum Action: ViewAction { + public enum ViewAction { + case changeGradientButtonTapped + } + + case destination(PresentationAction) + case view(ViewAction) + } + + public init() {} + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .view(.changeGradientButtonTapped): + state.destination = .changeGradient( + SelectGradientFeature.State(gradient: state.accountForDisplay.appearanceId) + ) + return .none + + case let .destination(.presented(.changeGradient(.delegate(.selected(newGradient))))): + state.accountForDisplay.appearanceId = newGradient + state.destination = nil + return .run { [address = state.accountForDisplay.address] send in + var account = try accountsClient.accountByAddress(address) + account.appearanceId = newGradient + try await accountsClient.updateAccount(account) + } + + default: + return .none + } + } + .ifLet(\.$destination, action: \.destination) + } +} + +extension AccountDetailsFeature { + + @ViewAction(for: AccountDetailsFeature.self) + public struct View: SwiftUI.View { + @Bindable public var store: StoreOf + public var body: some SwiftUI.View { + NavigationView { + VStack { + AccountView(accountForDisplay: store.accountForDisplay, format: .full) + + Button("Change Gradient") { + send(.changeGradientButtonTapped) + } + } + } + .sheet( + item: $store.scope(state: \.destination?.changeGradient, action: \.destination.changeGradient) + ) { store in + SelectGradientFeature.View(store: store) + } + } + } +} + diff --git a/examples/iOS/Backend/Sources/Planbok/Features/AccountsFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/AccountsFeature.swift index 9224c647b..13bbf893a 100644 --- a/examples/iOS/Backend/Sources/Planbok/Features/AccountsFeature.swift +++ b/examples/iOS/Backend/Sources/Planbok/Features/AccountsFeature.swift @@ -1,72 +1,50 @@ import Sargon -import SargonUniFFI +import ComposableArchitecture + @Reducer public struct AccountsFeature { + @Dependency(AccountsClient.self) var accountsClient + public init() {} - public var body: some ReducerOf { - Reduce { state, action in - switch action { - - case .view(.createNewAccountButtonTapped): - return .send(.delegate(.createNewAccount)) - - case .view(.deleteWalletButtonTapped): - return .send(.delegate(.deleteWallet)) - - default: return .none - } - } - } - @ObservableState public struct State: Equatable { - public var walletHolder: WalletHolder - - // FIXME: We really do not want this - mutating func refresh() { - walletHolder.refresh() - } - - public var profile: Profile { - walletHolder.wallet.profile() - } - - public var currentNetworkID: NetworkId { - profile.appPreferences.gateways.current.network.id - } - - public var network: ProfileNetwork { - profile.networks.first(where: { $0.id == currentNetworkID })! - } - - public var accounts: Accounts { - network.accounts - } - - public init(walletHolder: WalletHolder) { - self.walletHolder = walletHolder - } - - public init(wallet: Wallet) { - self.init(walletHolder: .init(wallet: wallet)) - } + @SharedReader(.network) var network + @SharedReader(.accountsForDisplay) var accountsForDisplay } public enum Action: ViewAction { public enum ViewAction { + case accountCardTapped(AccountForDisplay) case createNewAccountButtonTapped - case deleteWalletButtonTapped } public enum DelegateAction { - case createNewAccount - case deleteWallet + case createNewAccount(index: Int) + case showDetailsFor(AccountForDisplay) } case view(ViewAction) case delegate(DelegateAction) } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + + case .view(.createNewAccountButtonTapped): + return .send(.delegate(.createNewAccount(index: state.accountsForDisplay.count))) + + case let .view(.accountCardTapped(account)): + return .send(.delegate(.showDetailsFor(account))) + + default: return .none + } + } + } +} + +extension AccountsFeature { @ViewAction(for: AccountsFeature.self) public struct View: SwiftUI.View { @@ -80,9 +58,17 @@ public struct AccountsFeature { VStack { Text("Accounts").font(.largeTitle) - ForEach(store.state.accounts) { account in - VStack { - AccountView(account: account) + if store.state.accountsForDisplay.isEmpty { + Text("You dont have any accounts on \(store.state.network.description)") + } else { + ScrollView { + ForEach(store.state.accountsForDisplay) { accountForDisplay in + VStack { + AccountCardView(accountForDisplay: accountForDisplay) { + send(.accountCardTapped(accountForDisplay)) + } + } + } } } @@ -92,9 +78,6 @@ public struct AccountsFeature { send(.createNewAccountButtonTapped) } - Button("Delete Wallet", role: .destructive) { - send(.deleteWalletButtonTapped) - } } .padding() } diff --git a/examples/iOS/Backend/Sources/Planbok/Features/AppFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/AppFeature.swift index 4029b3dde..f0ba43bca 100644 --- a/examples/iOS/Backend/Sources/Planbok/Features/AppFeature.swift +++ b/examples/iOS/Backend/Sources/Planbok/Features/AppFeature.swift @@ -1,5 +1,5 @@ import Sargon -import SargonUniFFI +import ComposableArchitecture @Reducer public struct AppFeature { @@ -9,8 +9,34 @@ public struct AppFeature { case splash(SplashFeature.State) case onboarding(OnboardingFeature.State) case main(MainFeature.State) - public init() { - self = .splash(.init()) + + public init(isEmulatingFreshInstall: Bool = false) { + + let bios = BIOS.init( + drivers: .init( + networking: URLSession.shared, + secureStorage: Keychain(service: "rdx.works.planbok"), + entropyProvider: EntropyProvider.shared, + hostInfo: HostInfo( + appVersion: "0.0.1" + ), + logging: Log.shared, + eventBus: EventBus.shared, + fileSystem: FileSystem.shared, + unsafeStorage: UnsafeStorage.init( + userDefaults: .init( + suiteName: "rdx.works" + )! + ) + ) + ) + + BIOS.settingShared( + shared: bios, + isEmulatingFreshInstall: isEmulatingFreshInstall + ) + + self = .splash(.init(isEmulatingFreshInstall: true)) } } @@ -20,50 +46,30 @@ public struct AppFeature { case main(MainFeature.Action) } - public struct View: SwiftUI.View { - public let store: StoreOf - public init(store: StoreOf) { - self.store = store - } - public var body: some SwiftUI.View { - switch store.state { - case .splash: - if let store = store.scope(state: \.splash, action: \.splash) { - SplashFeature.View(store: store) - } - case .onboarding: - if let store = store.scope(state: \.onboarding, action: \.onboarding) { - OnboardingFeature.View(store: store) - } - case .main: - if let store = store.scope(state: \.main, action: \.main) { - MainFeature.View(store: store) - } - } - } - } - - public init() {} public var body: some ReducerOf { Reduce { state, action in switch action { - case let .splash(.delegate(.walletInitialized(wallet, hasAccount))): - if hasAccount { - state = .main(MainFeature.State(wallet: wallet)) + case let .splash(.delegate(.booted(hasAnyAccountOnAnyNetwork))): + if hasAnyAccountOnAnyNetwork { + state = .main(MainFeature.State()) } else { - state = .onboarding(OnboardingFeature.State(wallet: wallet)) + state = .onboarding(OnboardingFeature.State()) } return .none - case let .onboarding(.delegate(.createdAccount(with: walletHolder))): - state = .main(MainFeature.State(walletHolder: walletHolder)) + case .onboarding(.delegate(.done)): + state = .main(MainFeature.State()) return .none case .main(.delegate(.deletedWallet)): - state = .onboarding(OnboardingFeature.State(wallet: Wallet.generateNewBDFSAndEmptyProfile())) + state = .onboarding(OnboardingFeature.State()) + return .none + + case .main(.delegate(.emulateFreshInstall)): + state = AppFeature.State(isEmulatingFreshInstall: true) return .none default: @@ -82,3 +88,28 @@ public struct AppFeature { } } +extension AppFeature { + public struct View: SwiftUI.View { + public let store: StoreOf + public init(store: StoreOf) { + self.store = store + } + + public var body: some SwiftUI.View { + switch store.state { + case .splash: + if let store = store.scope(state: \.splash, action: \.splash) { + SplashFeature.View(store: store) + } + case .onboarding: + if let store = store.scope(state: \.onboarding, action: \.onboarding) { + OnboardingFeature.View(store: store) + } + case .main: + if let store = store.scope(state: \.main, action: \.main) { + MainFeature.View(store: store) + } + } + } + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Features/Flows/CreateAccount/CreateAccountFlowFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/Flows/CreateAccount/CreateAccountFlowFeature.swift index 1a7ec9b44..e342937df 100644 --- a/examples/iOS/Backend/Sources/Planbok/Features/Flows/CreateAccount/CreateAccountFlowFeature.swift +++ b/examples/iOS/Backend/Sources/Planbok/Features/Flows/CreateAccount/CreateAccountFlowFeature.swift @@ -1,27 +1,15 @@ import Sargon -import SargonUniFFI +import ComposableArchitecture @Reducer public struct CreateAccountFlowFeature { - - @Reducer(state: .equatable) - public enum Path { - case selectGradient(SelectGradientFeature) - } - + @ObservableState public struct State: Equatable { - public let walletHolder: WalletHolder - public var path = StackState() 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 init(index: Int) { + self.nameAccount = NameNewAccountFeature.State(index: index) } } @@ -29,31 +17,11 @@ public struct CreateAccountFlowFeature { 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) - } - } - } - } - } + @Dependency(AccountsClient.self) var accountsClient public init() {} @@ -66,48 +34,36 @@ public struct CreateAccountFlowFeature { 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 .run { send in + try await accountsClient.createAndSaveAccount(name) + await send(.delegate(.createdAccount)) + } catch: { _, error in + fatalError("TODO error handling: \(error)") } - return .none - case .nameAccount(.view): + case .nameAccount(_): return .none case .delegate: return .none } } - .forEach(\.path, action: \.path) } } + +extension CreateAccountFlowFeature { + public struct View: SwiftUI.View { + + @Bindable var store: StoreOf + public init(store: StoreOf) { + self.store = store + } + + public var body: some SwiftUI.View { + NameNewAccountFeature.View( + store: store.scope(state: \.nameAccount, action: \.nameAccount) + ) + } + } + +} diff --git a/examples/iOS/Backend/Sources/Planbok/Features/Flows/CreateAccount/Steps/NameAccountFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/Flows/CreateAccount/Steps/NameAccountFeature.swift index acf5bffe6..013a47fa4 100644 --- a/examples/iOS/Backend/Sources/Planbok/Features/Flows/CreateAccount/Steps/NameAccountFeature.swift +++ b/examples/iOS/Backend/Sources/Planbok/Features/Flows/CreateAccount/Steps/NameAccountFeature.swift @@ -1,19 +1,15 @@ import Sargon -import SargonUniFFI +import ComposableArchitecture @Reducer public struct NameNewAccountFeature { @ObservableState public struct State: Equatable { - public let walletHolder: WalletHolder - public var accountName = "" + public var accountName: String public var errorMessage: String? - public init(walletHolder: WalletHolder) { - self.walletHolder = walletHolder - } - public init(wallet: Wallet) { - self.init(walletHolder: .init(wallet: wallet)) + public init(index: Int = 0) { + self.accountName = "Unnamed \(index)" } } @@ -30,33 +26,6 @@ public struct NameNewAccountFeature { case view(ViewAction) } - @ViewAction(for: NameNewAccountFeature.self) - public struct View: SwiftUI.View { - @Bindable public var store: StoreOf - public init(store: StoreOf) { - self.store = store - } - public var body: some SwiftUI.View { - VStack { - Text("Name Account").font(.largeTitle) - Spacer() - LabeledTextField(label: "Account Name", text: $store.accountName.sending(\.view.accountNameChanged)) - if let error = store.state.errorMessage { - Text("\(error)") - .foregroundStyle(Color.red) - .font(.footnote) - .fontWeight(.bold) - } - Spacer() - Button("Continue") { - send(.continueButtonTapped) - } - .buttonStyle(.borderedProminent) - } - .padding() - } - } - public init() {} public var body: some ReducerOf { @@ -84,3 +53,32 @@ public struct NameNewAccountFeature { } } } + +extension NameNewAccountFeature { + @ViewAction(for: NameNewAccountFeature.self) + public struct View: SwiftUI.View { + @Bindable public var store: StoreOf + public init(store: StoreOf) { + self.store = store + } + public var body: some SwiftUI.View { + VStack { + Text("Name Account").font(.largeTitle) + Spacer() + LabeledTextField(label: "Account Name", text: $store.accountName.sending(\.view.accountNameChanged)) + if let error = store.state.errorMessage { + Text("\(error)") + .foregroundStyle(Color.red) + .font(.footnote) + .fontWeight(.bold) + } + Spacer() + Button("Continue") { + send(.continueButtonTapped) + } + .buttonStyle(.borderedProminent) + } + .padding() + } + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/ImportProfileFlowFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/ImportProfileFlowFeature.swift new file mode 100644 index 000000000..cc4cfd050 --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/ImportProfileFlowFeature.swift @@ -0,0 +1,125 @@ +import Sargon +import UniformTypeIdentifiers +import Foundation +import ComposableArchitecture + +@Reducer +public struct ImportProfileFlowFeature { + + @Dependency(ProfileClient.self) var profileClient + + @Reducer(state: .equatable) + public enum Path { + case inputDecryptionPassword(InputDecryptionPasswordFeature) + } + + @ObservableState + public struct State: Equatable { + public var path = StackState() + public var selectFile: SelectFileFeature.State + + public init() { + self.selectFile = SelectFileFeature.State() + } + } + + public enum Action { + public enum DelegateAction { + case imported + case failed + } + case path(StackAction) + + case selectFile(SelectFileFeature.Action) + case delegate(DelegateAction) + } + + public init() {} + + public var body: some ReducerOf { + Scope(state: \.selectFile, action: \.selectFile) { + SelectFileFeature() + } + + Reduce { state, action in + switch action { + + case let .selectFile(.delegate(.analyzedContentsOfFile(contents: data, analysis: profileFileContents))): + switch profileFileContents { + + case .notProfile: + return .send(.delegate(.failed)) + + case .encryptedProfile: + state.path.append(.inputDecryptionPassword(InputDecryptionPasswordFeature.State(encryptedProfile: data))) + return .none + + case let .plaintextProfile(plaintextProfile): + return importProfile(&state, plaintextProfile) + + } + + case .path(let pathAction): + switch pathAction { + + case let .element( + id: _, + action: .inputDecryptionPassword(.delegate(.inputtedPassword(encryptedProfile, decryptionPassword))) + ): + do { + let decrypted = try profileClient.decryptEncryptedProfile(encryptedProfile, decryptionPassword) + state.path = .init() + return importProfile(&state, decrypted) + } catch { + log.error("Failed to decrypt encrypted profile, error: \(error)") + } + + case .element(id: _, action: _): + return .none + case .popFrom(id: _): + return .none + case .push(id: _, state: _): + return .none + } + return .none + + case .selectFile(_): + return .none + + case .delegate: + return .none + } + } + .forEach(\.path, action: \.path) + } + + func importProfile(_ state: inout State, _ profile: Profile) -> Effect { + return .run { send in + try await profileClient.importProfile(profile) + await send(.delegate(.imported)) + } + } +} + +extension ImportProfileFlowFeature { + 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)) { + SelectFileFeature.View( + store: store.scope(state: \.selectFile, action: \.selectFile) + ) + } destination: { store in + switch store.case { + case let .inputDecryptionPassword(store): + InputDecryptionPasswordFeature.View(store: store) + } + } + } + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/Steps/FileDocument+Profile.swift b/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/Steps/FileDocument+Profile.swift new file mode 100644 index 000000000..16aff6bbc --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/Steps/FileDocument+Profile.swift @@ -0,0 +1,57 @@ +import ComposableArchitecture +import Sargon +import SwiftUI +import UniformTypeIdentifiers + +// MARK: - NoJSONDataFound +struct NoJSONDataFound: Error {} + +// MARK: - FileContentIsNotProfile +struct FileContentIsNotProfile: LocalizedError { + var errorDescription: String? { + "Invalid backup file." + } +} + +// MARK: - ExportableProfileFile +/// An exportable (and thus importable) Profile file, either encrypted or plaintext. +public enum ExportableProfileFile: FileDocument, Sendable, Hashable { + case plaintext(Profile) + case encrypted(Data) +} + + +extension ExportableProfileFile { + public static let readableContentTypes: [UTType] = [.profile] + + public init(configuration: ReadConfiguration) throws { + guard let data = configuration.file.regularFileContents + else { + throw NoJSONDataFound() + } + try self.init(data: data) + } + + public init(data: Data) throws { + switch Profile.analyzeFile(contents: data) { + case .encryptedProfile: + self = .encrypted(data) + case .notProfile: + throw FileContentIsNotProfile() + case let .plaintextProfile(plaintextProfile): + self = .plaintext(plaintextProfile) + } + } + + public func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { + @Dependency(\.encode) var encode + + switch self { + case let .plaintext(plaintext): + let jsonData = plaintext.profileSnapshot() + return FileWrapper(regularFileWithContents: jsonData) + case let .encrypted(encryptedSnapshot): + return FileWrapper(regularFileWithContents: encryptedSnapshot) + } + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/Steps/InputDecryptionPasswordFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/Steps/InputDecryptionPasswordFeature.swift new file mode 100644 index 000000000..26c25d674 --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/Steps/InputDecryptionPasswordFeature.swift @@ -0,0 +1,77 @@ +import Sargon +import UniformTypeIdentifiers +import Foundation +import ComposableArchitecture + +@Reducer +public struct InputDecryptionPasswordFeature { + + @CasePathable + public enum Action: ViewAction { + public enum DelegateAction { + case inputtedPassword(encryptedProfile: Data, decryptionPassword: String) + } + + @CasePathable + public enum ViewAction { + case passwordChanged(String) + case confirmPasswordButtonTapped + } + case delegate(DelegateAction) + case view(ViewAction) + } + + @ObservableState + public struct State: Equatable { + public let encryptedProfile: Data + public var password = "" + public init(encryptedProfile: Data) { + self.encryptedProfile = encryptedProfile + } + } + + + public init() {} + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case let .view(.passwordChanged(password)): + state.password = password + return .none + + case .view(.confirmPasswordButtonTapped): + return .send(.delegate(.inputtedPassword(encryptedProfile: state.encryptedProfile, decryptionPassword: state.password))) + + case .delegate: + return .none + + } + } + } +} + +extension InputDecryptionPasswordFeature { + + @ViewAction(for: InputDecryptionPasswordFeature.self) + public struct View: SwiftUI.View { + + @Bindable public var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some SwiftUI.View { + VStack { + LabeledTextField(label: "Decryption password", text: $store.password.sending(\.view.passwordChanged)) + + Button("Confirm") { + send(.confirmPasswordButtonTapped) + } + .buttonStyle(.borderedProminent) + } + .padding() + } + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/Steps/SelectFileFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/Steps/SelectFileFeature.swift new file mode 100644 index 000000000..c5a7d09f4 --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Features/Flows/ImportProfileFlowFeature/Steps/SelectFileFeature.swift @@ -0,0 +1,113 @@ +import Sargon +import UniformTypeIdentifiers +import Foundation +import ComposableArchitecture + +extension UTType { + // FIXME: should we declare our own file format? For now we use require `.json` file extension. + public static let profile: Self = .json +} + + +extension String { + static let profileFileEncryptedPart = "encrypted" + private static let filenameProfileBase = "radix_wallet_backup_file" + static let filenameProfileNotEncrypted: Self = "\(filenameProfileBase).plaintext.json" + static let filenameProfileEncrypted: Self = "\(filenameProfileBase).\(profileFileEncryptedPart).json" +} + +struct LackedPermissionToAccessSecurityScopedResource: Error {} + +@Reducer +public struct SelectFileFeature { + + @CasePathable + public enum Action: ViewAction { + public enum DelegateAction { + case analyzedContentsOfFile(contents: Data, analysis: ProfileFileContents) + } + + @CasePathable + public enum ViewAction { + case openFileButtonTapped + case profileImportResult(Result) + case isPresentingFileImporterChanged(Bool) + } + case delegate(DelegateAction) + case view(ViewAction) + } + + @ObservableState + public struct State: Equatable { + public var isPresentingFileImporter = false + public init() {} + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .view(.openFileButtonTapped): + state.isPresentingFileImporter = true + return .none + + case let .view(.profileImportResult(.failure(error))): + log.error("Failed to read file, error: \(error)") + return .none + + case let .view(.profileImportResult(.success(profileURL))): + do { + guard profileURL.startAccessingSecurityScopedResource() else { + throw LackedPermissionToAccessSecurityScopedResource() + } + defer { profileURL.stopAccessingSecurityScopedResource() } + let data = try Data(contentsOf: profileURL) + + let analyzed = Profile.analyzeFile(contents: data) + return .send(.delegate(.analyzedContentsOfFile(contents: data, analysis: analyzed))) + + } catch { + log.error("Failed to import profile, error: \(error)") + } + return .none + + + case let .view(.isPresentingFileImporterChanged(isPresentingFileImporter)): + state.isPresentingFileImporter = isPresentingFileImporter + return .none + + case .delegate(_): + return .none + } + } + } +} + +extension SelectFileFeature { + + @ViewAction(for: SelectFileFeature.self) + public struct View: SwiftUI.View { + + @Bindable public var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some SwiftUI.View { + VStack { + Text("Select file") + + Button("Open file selector") { + send(.openFileButtonTapped) + } + } + .fileImporter( + isPresented: $store.isPresentingFileImporter.sending(\.view.isPresentingFileImporterChanged), + allowedContentTypes: [.profile], + onCompletion: { send(.profileImportResult($0.mapError { $0 as NSError })) } + ) + .navigationTitle("Open Profile file") + } + } + +} diff --git a/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/NewOrImportProfileFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/NewOrImportProfileFeature.swift new file mode 100644 index 000000000..8f7d1447a --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/NewOrImportProfileFeature.swift @@ -0,0 +1,77 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-02-16. +// + +import Foundation +import ComposableArchitecture +import Sargon +import SwiftUI + +@Reducer +public struct NewOrImportProfileFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public init() {} + } + + public enum Action: ViewAction { + + public enum DelegateAction { + case newProfile + case importProfile + } + + public enum ViewAction { + case newProfileButtonTapped + case importProfileButtonTapped + } + + case delegate(DelegateAction) + case view(ViewAction) + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + + case .view(.importProfileButtonTapped): + .send(.delegate(.importProfile)) + + case .view(.newProfileButtonTapped): + .send(.delegate(.newProfile)) + + case .delegate: + .none + + } + } + } +} + +extension NewOrImportProfileFeature { + + @ViewAction(for: NewOrImportProfileFeature.self) + public struct View: SwiftUI.View { + public let store: StoreOf + + public var body: some SwiftUI.View { + VStack { + Text("Existing or new user?").font(.title) + + Button("New Profile") { + send(.newProfileButtonTapped) + } + + Button("Import Profile") { + send(.importProfileButtonTapped) + } + } + .padding() + } + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/OnboardingFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/OnboardingFeature.swift index 7a08a2c7c..8669c6a55 100644 --- a/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/OnboardingFeature.swift +++ b/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/OnboardingFeature.swift @@ -1,44 +1,41 @@ +import SwiftUI import Sargon -import SargonUniFFI +import ComposableArchitecture @Reducer public struct OnboardingFeature { @Reducer(state: .equatable) public enum Path { + case newOrImportProfile(NewOrImportProfileFeature) case writeDownMnemonic(WriteDownMnemonicFeature) } @Reducer(state: .equatable) public enum Destination { case createAccount(CreateAccountFlowFeature) + case importProfile(ImportProfileFlowFeature) } @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 + public init() { 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 done } - + case destination(PresentationAction) case path(StackAction) case welcome(WelcomeFeature.Action) @@ -56,8 +53,18 @@ public struct OnboardingFeature { case .path(let pathAction): switch pathAction { + case .element(id: _, action: .writeDownMnemonic(.delegate(.done))): - return .send(.delegate(.createdAccount(with: state.walletHolder))) + return .send(.delegate(.done)) + + case .element(id: _, action: .newOrImportProfile(.delegate(.importProfile))): + state.destination = .importProfile(.init()) + return .none + + case .element(id: _, action: .newOrImportProfile(.delegate(.newProfile))): + state.destination = .createAccount(CreateAccountFlowFeature.State(index: 0)) + return .none + case .popFrom(id: _): return .none case .push(id: _, state: _): @@ -65,18 +72,28 @@ public struct OnboardingFeature { default: return .none } + case .welcome(.delegate(.done)): - state.destination = .createAccount(CreateAccountFlowFeature.State(walletHolder: state.walletHolder)) + + state.path.append(.newOrImportProfile(.init())) return .none - case .welcome(.view): + + + case .welcome(_): return .none + case .delegate: return .none + + case .destination(.presented(.importProfile(.delegate(.imported)))): + state.destination = nil + return .send(.delegate(.done)) + case .destination(.presented(.createAccount(.delegate(.createdAccount)))): state.destination = nil - state.path.append(.writeDownMnemonic(.init(walletHolder: state.walletHolder))) + state.path.append(.writeDownMnemonic(.init())) return .none - + default: return .none } @@ -84,7 +101,9 @@ public struct OnboardingFeature { .forEach(\.path, action: \.path) .ifLet(\.$destination, action: \.destination) } +} +extension OnboardingFeature { public struct View: SwiftUI.View { @Bindable var store: StoreOf @@ -99,24 +118,22 @@ public struct OnboardingFeature { ) } destination: { store in switch store.case { - case .writeDownMnemonic: - if let store = store.scope(state: \.writeDownMnemonic, action: \.writeDownMnemonic) { - WriteDownMnemonicFeature.View(store: store) - } + case let .newOrImportProfile(store): + NewOrImportProfileFeature.View(store: store) + case let .writeDownMnemonic(store): + WriteDownMnemonicFeature.View(store: store) } } .sheet( - item: $store.scope( - state: \.destination?.createAccount, - action: \.destination.createAccount - ) + item: $store.scope(state: \.destination?.createAccount, action: \.destination.createAccount) ) { store in CreateAccountFlowFeature.View(store: store) } - + .sheet( + item: $store.scope(state: \.destination?.importProfile, action: \.destination.importProfile) + ) { importProfileStore in + ImportProfileFlowFeature.View(store: importProfileStore) + } } - } - - } diff --git a/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/WelcomeFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/WelcomeFeature.swift index e95e2e6f2..83798c97a 100644 --- a/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/WelcomeFeature.swift +++ b/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/WelcomeFeature.swift @@ -6,6 +6,8 @@ // import Foundation +import ComposableArchitecture +import Sargon @Reducer public struct WelcomeFeature { @@ -27,6 +29,19 @@ public struct WelcomeFeature { case view(ViewAction) } + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .view(.continueButtonTapped): + .send(.delegate(.done)) + case .delegate: + .none + } + } + } +} + +extension WelcomeFeature { @ViewAction(for: WelcomeFeature.self) public struct View: SwiftUI.View { public let store: StoreOf @@ -56,15 +71,4 @@ The build artifacts of UniFFI are have three major components: .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/Backend/Sources/Planbok/Features/Flows/Onboarding/WriteDownMnemonicFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/WriteDownMnemonicFeature.swift index 44b0350d3..1f3cd468f 100644 --- a/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/WriteDownMnemonicFeature.swift +++ b/examples/iOS/Backend/Sources/Planbok/Features/Flows/Onboarding/WriteDownMnemonicFeature.swift @@ -6,20 +6,20 @@ // import Foundation +import ComposableArchitecture +import Sargon @Reducer public struct WriteDownMnemonicFeature { - - @Dependency(\.keychain) var keychain + @Dependency(MnemonicClient.self) var mnemonicClient + public init() {} @ObservableState public struct State: Equatable { - public let walletHolder: WalletHolder public var mnemonic: String? - public init(walletHolder: WalletHolder) { - self.walletHolder = walletHolder + public init() { } } @@ -31,9 +31,39 @@ public struct WriteDownMnemonicFeature { case revealMnemonicButtonTapped case continueButtonTapped } + public enum InternalAction { + case loadedPrivateHDFactor(PrivateHierarchicalDeterministicFactorSource) + } case delegate(DelegateAction) case view(ViewAction) + case `internal`(InternalAction) + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case let .internal(.loadedPrivateHDFactor(privateHDFactor)): + state.mnemonic = privateHDFactor.mnemonicWithPassphrase.mnemonic.phrase + return .none + + case .view(.revealMnemonicButtonTapped): + return .run { send in + let id = try SargonOS.shared.profile().factorSources.first!.id.extract(as: FactorSourceIdFromHash.self) + let privateHDFactor = try await mnemonicClient.loadMnemonic(id) + await send(.internal(.loadedPrivateHDFactor(privateHDFactor))) + } catch: { error, send in + fatalError("error \(error)") + } + case .view(.continueButtonTapped): + return .send(.delegate(.done)) + case .delegate: + return .none + } + } } +} + +extension WriteDownMnemonicFeature { @ViewAction(for: WriteDownMnemonicFeature.self) public struct View: SwiftUI.View { @@ -61,24 +91,4 @@ public struct WriteDownMnemonicFeature { } } - 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/Backend/Sources/Planbok/Features/GatewaysFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/GatewaysFeature.swift new file mode 100644 index 000000000..dde21e77f --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Features/GatewaysFeature.swift @@ -0,0 +1,93 @@ +import Sargon +import ComposableArchitecture + +@Reducer +public struct GatewaysFeature { + + @Dependency(GatewaysClient.self) var gatewaysClient + + @ObservableState + public struct State: Equatable { + @SharedReader(.savedGateways) var savedGateways + } + + @CasePathable + public enum Action: ViewAction { + @CasePathable + public enum ViewAction { + case gatewayTapped(Gateway, isCurrent: Bool) + } + case view(ViewAction) + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case let .view(.gatewayTapped(gateway, isCurrent)): + if isCurrent { + log.debug("Tapped \(gateway), but not switching since it is already current.") + return .none + } else { + return .run { _ in + try await gatewaysClient.switchGatewayTo(gateway) + } + } + } + } + } +} +extension GatewaysFeature { + @ViewAction(for: GatewaysFeature.self) + public struct View: SwiftUI.View { + + @Bindable public var store: StoreOf + public var body: some SwiftUI.View { + VStack { + Text("Saved gateways").font(.title) + + ScrollView { + ForEach(store.state.savedGateways.all.sorted()) { gateway in + let isCurrent = gateway == store.state.savedGateways.current + VStack { + GatewayView(gateway: gateway, isCurrent: isCurrent) { + send(.gatewayTapped(gateway, isCurrent: isCurrent)) + } + .padding(.bottom, 10) + } + } + } + } + .padding([.leading, .trailing], 20) + } + } +} + +extension Gateway: Comparable { + public static func < (lhs: Self, rhs: Self) -> Bool { + if lhs.networkID == .mainnet { return true } + if rhs.networkID == .mainnet { return false } + return lhs.networkID.rawValue < rhs.networkID.rawValue && lhs.url.absoluteString < rhs.url.absoluteString + } +} + +public struct GatewayView: SwiftUI.View { + public let gateway: Gateway + public let isCurrent: Bool + public let action: () -> Void + + public var body: some SwiftUI.View { + + Button.init(action: action, label: { + HStack { + Text(isCurrent ? "✅" : "☑️").font(.title) + VStack { + Text("\(gateway.network.displayDescription)").font(.body) + Text("\(gateway.networkID.toString())") + } + } + }) + .buttonStyle(.plain) + .frame(maxWidth: .infinity, alignment: .leading) + .cornerRadius(.small1) + } +} diff --git a/examples/iOS/Backend/Sources/Planbok/Features/MainFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/MainFeature.swift index 6226293a0..13f88cb29 100644 --- a/examples/iOS/Backend/Sources/Planbok/Features/MainFeature.swift +++ b/examples/iOS/Backend/Sources/Planbok/Features/MainFeature.swift @@ -1,57 +1,69 @@ import Sargon -import SargonUniFFI +import ComposableArchitecture @Reducer public struct MainFeature { + @Dependency(ProfileClient.self) var profileClient + @Dependency(AccountsClient.self) var accountsClient + + @Reducer(state: .equatable) + public enum Path { + case settings(SettingsFeature) + case accountDetails(AccountDetailsFeature) + } + @Reducer(state: .equatable) public enum Destination { case createAccount(CreateAccountFlowFeature) - case sampleValues(SampleValuesFeature) - case alert(AlertState) - public enum Alert { - case confirmedDeleteWallet + case deleteProfileAlert(AlertState) + + public enum DeleteProfileAlert { + case confirmedDeleteProfileBDFSThenOnboard + case confirmedEmulateFreshInstallThenTerminate } } @ObservableState public struct State: Equatable { - + @SharedReader(.network) var network @Presents var destination: Destination.State? - + public var path = StackState() 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 init() { + self.accounts = AccountsFeature.State() } + } @CasePathable public enum Action: ViewAction { + @CasePathable public enum ViewAction { - case sampleValuesButtonTapped + case settingsButtonTapped + case deleteWalletButtonTapped } + @CasePathable public enum DelegateAction { case deletedWallet + case emulateFreshInstall } + + case view(ViewAction) case destination(PresentationAction) + case path(StackAction) + case accounts(AccountsFeature.Action) case delegate(DelegateAction) } - @Dependency(\.keychain) var keychain public init() {} public var body: some ReducerOf { @@ -61,56 +73,102 @@ public struct MainFeature { Reduce { state, action in switch action { - case .view(.sampleValuesButtonTapped): - state.destination = .sampleValues(SampleValuesFeature.State()) - return .none - - case .accounts(.delegate(.deleteWallet)): - state.destination = .alert(.init( + case .path(let pathAction): + switch pathAction { + + case let .element(id: _, action: action): + switch action { + case .accountDetails(_): + return .none + case .settings(_): + return .none + } + + case .popFrom(id: _): + return .none + case .push(id: _, state: _): + return .none + } + + case .view(.deleteWalletButtonTapped): + state.destination = .deleteProfileAlert(.init( title: TextState("Delete wallet?"), message: TextState("Warning"), buttons: [ .cancel(TextState("Cancel")), .destructive( - TextState("Delete Wallet and mnemonic"), - action: .send(.confirmedDeleteWallet) + TextState("Delete Profile & BDFS -> Onboard"), + action: .send(.confirmedDeleteProfileBDFSThenOnboard) + ), + .destructive( + TextState("Emulate Fresh Install -> Restart"), + action: .send(.confirmedEmulateFreshInstallThenTerminate) ) ] )) return .none - case .accounts(.delegate(.createNewAccount)): + + case .view(.settingsButtonTapped): + state.path.append(.settings(SettingsFeature.State())) + return .none + + case let .accounts(.delegate(.showDetailsFor(accountForDisplay))): + state.path.append(.accountDetails(AccountDetailsFeature.State(accountForDisplay: accountForDisplay))) + return .none + + + case let .accounts(.delegate(.createNewAccount(index))): state.destination = .createAccount( - CreateAccountFlowFeature.State( - walletHolder: state.walletHolder - ) + CreateAccountFlowFeature.State(index: index) ) return .none - case .destination(.presented(.alert(.confirmedDeleteWallet))): - print("⚠️ Confirmed deletion of wallet") + case .destination(.presented(.deleteProfileAlert(.confirmedEmulateFreshInstallThenTerminate))): + log.notice("Confirmed deletion of Profile & BDFS") state.destination = nil - let profileID = state.walletHolder.wallet.profile().id - do { - try keychain.deleteDataForKey(SecureStorageKey.profileSnapshot(profileId: profileID)) - try keychain.deleteDataForKey(SecureStorageKey.activeProfileId) - return .send(.delegate(.deletedWallet)) - } catch { - fatalError("Fix error handling, error: \(error)") + return .run { send in + try await profileClient.emulateFreshInstallOfAppThenRestart() + await send(.delegate(.emulateFreshInstall)) } - + + case .destination(.presented(.deleteProfileAlert(.confirmedDeleteProfileBDFSThenOnboard))): + log.notice("Confirmed deletion of Profile & BDFS (will then onboard)") + state.destination = nil + return .run { send in + try await profileClient.deleteProfileAndMnemonicsThenCreateNew() + await send(.delegate(.deletedWallet)) + } + + case .destination(.presented(.createAccount(.delegate(.createdAccount)))): state.destination = nil - state.accounts.refresh() // FIXME: we really do not want this. return .none - + default: return .none } } + .forEach(\.path, action: \.path) .ifLet(\.$destination, action: \.destination) } - + +} + +public struct DeveloperDisclaimerBanner: View { + public var body: some View { + Text("Connected to a test network, not Radix main network") + .frame(maxWidth: .infinity, alignment: .center) + .padding(4) + .background(Color.app.orange2) + .font(.system(size: 12)) + } + + public init() {} +} + + +extension MainFeature { @ViewAction(for: MainFeature.self) public struct View: SwiftUI.View { @@ -121,44 +179,51 @@ public struct MainFeature { } public var body: some SwiftUI.View { - NavigationStack { + if store.network != .mainnet { + DeveloperDisclaimerBanner() + } + NavigationStack(path: $store.scope(state: \.path, action: \.path)) { VStack { VStack { Text("ProfileID:") - Text("\(store.state.walletHolder.wallet.profile().id)") + Text("\(SargonOS.shared.profile.id)") } AccountsFeature.View( store: store.scope(state: \.accounts, action: \.accounts) ) - } - .sheet( - item: $store.scope( - state: \.destination?.sampleValues, - action: \.destination.sampleValues - ) - ) { store in - NavigationView { - SampleValuesFeature.View(store: store) + + Button("Delete Wallet", role: .destructive) { + send(.deleteWalletButtonTapped) } } - .sheet( - item: $store.scope( - state: \.destination?.createAccount, - action: \.destination.createAccount - ) - ) { store in - CreateAccountFlowFeature.View(store: store) - } - .alert($store.scope(state: \.destination?.alert, action: \.destination.alert)) .toolbar { ToolbarItem(placement: .primaryAction) { - Button("Samples") { - send(.sampleValuesButtonTapped) + Button("Settings") { + send(.settingsButtonTapped) } } } + } destination: { store in + switch store.case { + case let .settings(store): + SettingsFeature.View(store: store) + case let .accountDetails(store): + AccountDetailsFeature.View(store: store) + } + } + .sheet( + item: $store.scope( + state: \.destination?.createAccount, + action: \.destination.createAccount + ) + ) { store in + CreateAccountFlowFeature.View(store: store) } + .alert($store.scope(state: \.destination?.deleteProfileAlert, action: \.destination.deleteProfileAlert)) + } + + } } diff --git a/examples/iOS/Backend/Sources/Planbok/Features/PreviewAddressesFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/PreviewAddressesFeature.swift deleted file mode 100644 index 04070b401..000000000 --- a/examples/iOS/Backend/Sources/Planbok/Features/PreviewAddressesFeature.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Sargon -import SargonUniFFI - -#if DEBUG -public struct SampleAddressesView: SwiftUI.View { - public var body: some SwiftUI.View { - VStack(alignment: .leading, spacing: .large2) { - ForEach(Address.sampleValues, id: \.self) { address in - Text("`\(address.address)`") - .font(.footnote) - .lineLimit(3) - .multilineTextAlignment(.leading) - .fixedSize(horizontal: false, vertical: true) - } - } - } -} - -#endif diff --git a/examples/iOS/Backend/Sources/Planbok/Features/SampleValuesFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/SampleValuesFeature.swift deleted file mode 100644 index 3936731fa..000000000 --- a/examples/iOS/Backend/Sources/Planbok/Features/SampleValuesFeature.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Sargon -import SargonUniFFI - -#if DEBUG -@Reducer -public struct SampleValuesFeature { - - @ObservableState - public struct State: Equatable {} - - public enum Action {} - - public init() {} - - public struct View: SwiftUI.View { - public let store: StoreOf - public var body: some SwiftUI.View { - Form { - Section("TX Manifests") { - Text("`\(TransactionManifest.sample.description)`") - } - .font(.footnote) - - Section("Addresses") { - SampleAddressesView() - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding() - } - } -} - -#endif diff --git a/examples/iOS/Backend/Sources/Planbok/Features/Flows/CreateAccount/Steps/SelectGradientFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/SelectGradientFeature.swift similarity index 89% rename from examples/iOS/Backend/Sources/Planbok/Features/Flows/CreateAccount/Steps/SelectGradientFeature.swift rename to examples/iOS/Backend/Sources/Planbok/Features/SelectGradientFeature.swift index d06a0d18b..e2b5aebaa 100644 --- a/examples/iOS/Backend/Sources/Planbok/Features/Flows/CreateAccount/Steps/SelectGradientFeature.swift +++ b/examples/iOS/Backend/Sources/Planbok/Features/SelectGradientFeature.swift @@ -7,20 +7,17 @@ import Foundation import Sargon -import SargonUniFFI +import ComposableArchitecture @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! + gradient: AppearanceID ) { - self.name = name self.gradient = gradient } } @@ -28,7 +25,7 @@ public struct SelectGradientFeature { @CasePathable public enum Action: ViewAction { public enum Delegate { - case selected(AppearanceID, DisplayName) + case selected(AppearanceID) } @CasePathable public enum ViewAction { @@ -39,6 +36,29 @@ public struct SelectGradientFeature { case delegate(Delegate) } + 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))) + + default: + return .none + + } + } + } +} + +extension SelectGradientFeature { + @ViewAction(for: SelectGradientFeature.self) public struct View: SwiftUI.View { public let store: StoreOf @@ -84,24 +104,4 @@ public struct SelectGradientFeature { .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/Backend/Sources/Planbok/Features/SettingsFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/SettingsFeature.swift new file mode 100644 index 000000000..efa6bf9d7 --- /dev/null +++ b/examples/iOS/Backend/Sources/Planbok/Features/SettingsFeature.swift @@ -0,0 +1,113 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-05. +// + +import Foundation +import Sargon +import ComposableArchitecture +import SwiftUI + +@Reducer +public struct SettingsFeature { + @Dependency(AccountsClient.self) var accountsClient + + @Reducer(state: .equatable) + public enum Destination { + case createManyAccountsAlert(AlertState) + + public enum CreateManyAccountsAlert: Int, CaseIterable { + case create10 = 10 + case create20 = 20 + case create50 = 50 + case create100 = 100 + case create200 = 200 + case create500 = 500 + case create1000 = 1000 + } + } + + @ObservableState + public struct State: Equatable { + @Presents var destination: Destination.State? + } + + @CasePathable + public enum Action: ViewAction { + + @CasePathable + public enum ViewAction { + case createManyAccountsButtonTapped + } + + case view(ViewAction) + case destination(PresentationAction) + } + + public init() {} + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .view(.createManyAccountsButtonTapped): + + state.destination = .createManyAccountsAlert(.init( + title: TextState("How many?"), + message: TextState("Will batch create many accounts and then perform one single save action."), + buttons: [ + .cancel(TextState("Cancel")) + ] + Destination.CreateManyAccountsAlert.allCases.map { action in + ButtonState.init(action: action, label: { + TextState("Create \(action.rawValue)") + }) + } + )) + return .none + + case let .destination(.presented(.createManyAccountsAlert(action))): + state.destination = nil + let count = UInt16(action.rawValue) + return .run { send in + try await accountsClient.batchCreateManySavedAccounts(count) + } + + default: + return .none + + } + } + .ifLet(\.$destination, action: \.destination) + } +} + +extension SettingsFeature { + + @ViewAction(for: SettingsFeature.self) + public struct View: SwiftUI.View { + + @Bindable public var store: StoreOf + public var body: some SwiftUI.View { + VStack { + Text("Settings").font(.largeTitle) + Spacer() + GatewaysFeature.View( + store: Store( + initialState: GatewaysFeature.State() + ) { + GatewaysFeature() + } + ) + Spacer() + Button("Create Many Accounts") { + send(.createManyAccountsButtonTapped) + } + } + .padding(.bottom, 100) + .alert($store.scope(state: \.destination?.createManyAccountsAlert, action: \.destination.createManyAccountsAlert)) + } + } + +} + diff --git a/examples/iOS/Backend/Sources/Planbok/Features/SplashFeature.swift b/examples/iOS/Backend/Sources/Planbok/Features/SplashFeature.swift index 7ff24da06..bea77ac9c 100644 --- a/examples/iOS/Backend/Sources/Planbok/Features/SplashFeature.swift +++ b/examples/iOS/Backend/Sources/Planbok/Features/SplashFeature.swift @@ -1,29 +1,30 @@ import Sargon -import SargonUniFFI +import ComposableArchitecture @Reducer public struct SplashFeature { @Dependency(\.continuousClock) var clock - @Dependency(\.keychain) var keychain - @ObservableState public struct State { - public init() {} + let isEmulatingFreshInstall: Bool + public init(isEmulatingFreshInstall: Bool = false) { + self.isEmulatingFreshInstall = isEmulatingFreshInstall + } } - - public enum Action: ViewAction { - public enum DelegateAction { - case walletInitialized(Wallet, hasAccount: Bool) + + public enum Action: ViewAction, Sendable { + public enum DelegateAction: Sendable { + case booted(hasAnyAccountOnAnyNetwork: Bool) } - public enum ViewAction { + public enum ViewAction: Sendable { case appear } case delegate(DelegateAction) case view(ViewAction) } - + @ViewAction(for: SplashFeature.self) public struct View: SwiftUI.View { public let store: StoreOf @@ -31,7 +32,7 @@ public struct SplashFeature { self.store = store } public var body: some SwiftUI.View { - Image("Splash", bundle: Bundle.module) + Image("Splash", bundle: Bundle.module) .resizable() .ignoresSafeArea(edges: [.top, .bottom]) .onAppear { @@ -39,46 +40,36 @@ public struct SplashFeature { } } } - + + @Dependency(\.mainQueue) var mainQueue public init() {} - + public var body: some ReducerOf { - Reduce { state, action in + Reduce { + state, + action in switch action { + case .view(.appear): - .run { send in - let secureStorage = Keychain.shared - try await clock.sleep(for: .milliseconds(1200)) - if try keychain.loadData(SecureStorageKey.activeProfileId) != nil { - let wallet = try Wallet.byLoadingProfile(secureStorage: secureStorage) - let profile = wallet.profile() - let hasAccount = profile.networks.first?.accounts.isEmpty == false - await send(.delegate(.walletInitialized(wallet, hasAccount: hasAccount))) - } else { - await send(.delegate(.walletInitialized( - Wallet.generateNewBDFSAndEmptyProfile(secureStorage: secureStorage), - hasAccount: false) - )) - } - } + struct SplashID: Hashable { } + return .run { [isEmulatingFreshInstall = state.isEmulatingFreshInstall] send in + + let os = try await SargonOS.createdSharedBootingWith( + bios: BIOS.shared, + isEmulatingFreshInstall: isEmulatingFreshInstall + ) + await send( + .delegate(.booted( + hasAnyAccountOnAnyNetwork: os.hasAnyAccountOnAnyNetwork() + )) + ) + } + .debounce(id: SplashID(), for: 0.8, scheduler: mainQueue) + case .delegate: - .none + return .none } } } } -extension Wallet { - static func generateNewBDFSAndEmptyProfile(secureStorage: SecureStorage = Keychain.shared) -> Wallet { - do { - return try Wallet.byCreatingNewProfileAndSecretsWithEntropy( - entropy: .init(bagOfBytes: BagOfBytes.random(byteCount: 32)), - walletClientModel: .iphone, - walletClientName: "Unknown iPhone", - secureStorage: secureStorage - ) - } catch { - fatalError("TODO Handle errors: error \(error)") - } - } -} diff --git a/examples/iOS/Backend/Sources/Planbok/View/Views/AccountView.swift b/examples/iOS/Backend/Sources/Planbok/View/Views/AccountView.swift index 54a22b28f..538092e84 100644 --- a/examples/iOS/Backend/Sources/Planbok/View/Views/AccountView.swift +++ b/examples/iOS/Backend/Sources/Planbok/View/Views/AccountView.swift @@ -1,26 +1,44 @@ import Sargon -import SargonUniFFI +import ComposableArchitecture -public struct AccountView: SwiftUI.View { - public let account: Account +public struct AccountCardView: SwiftUI.View { + public let accountForDisplay: AccountForDisplay + public let action: () -> Void + + public var body: some SwiftUI.View { + + Button.init(action: action, label: { + AccountView(accountForDisplay: accountForDisplay) + }) + .buttonStyle(.plain) + .cornerRadius(.small1) + } +} +public struct AccountView: SwiftUI.View { + public let accountForDisplay: AccountForDisplay + public let format: AddressFormat + + init(accountForDisplay: AccountForDisplay, format: AddressFormat = .default) { + self.accountForDisplay = accountForDisplay + self.format = format + } + public var body: some SwiftUI.View { - VStack(alignment: .leading, spacing: .medium3) { VStack(alignment: .leading, spacing: .zero) { - Text(account.displayName.value) + Text(accountForDisplay.displayName.value) .lineLimit(1) .foregroundColor(.white) .frame(maxWidth: .infinity, alignment: .leading) - AddressView(account.address) - .foregroundColor(.app.whiteTransparent) + AddressView(accountForDisplay.address, format: format) + .foregroundColor(.app.whiteTransparent) } .padding(.horizontal, .medium1) .padding(.vertical, .medium2) - .background(account.appearanceID.gradient) - .cornerRadius(.small1) - - } + .background(accountForDisplay.appearanceId.gradient) + + } } diff --git a/examples/iOS/Backend/Sources/Planbok/View/Views/LabeledTextField.swift b/examples/iOS/Backend/Sources/Planbok/View/Views/LabeledTextField.swift index 1c21bdfae..47993ba8b 100644 --- a/examples/iOS/Backend/Sources/Planbok/View/Views/LabeledTextField.swift +++ b/examples/iOS/Backend/Sources/Planbok/View/Views/LabeledTextField.swift @@ -8,6 +8,8 @@ public struct LabeledTextField: SwiftUI.View { VStack(alignment: .leading) { Text(label).padding(.leading, 5) TextField(label, text: $text) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) } .textFieldStyle(.roundedBorder) } diff --git a/examples/iOS/Backend/Tests/PlanbokTests/PlanbokTests.swift b/examples/iOS/Backend/Tests/PlanbokTests/PlanbokTests.swift index c242f0190..f20bc99cb 100644 --- a/examples/iOS/Backend/Tests/PlanbokTests/PlanbokTests.swift +++ b/examples/iOS/Backend/Tests/PlanbokTests/PlanbokTests.swift @@ -1,7 +1,38 @@ import XCTest @testable import Planbok +@testable import Sargon -final class PlanbokTests: XCTestCase { - func testExample() throws { +class TestCase: XCTestCase { + + override func setUp() async throws { + try await super.setUp() + let _ = try await SargonOS.createdSharedBootingWith(bios: BIOS.insecure(), isEmulatingFreshInstall: true) + } +} + +final class PlanbokTests: TestCase { + + + func test_shared_reader_network_is_on_mainnet_for_new_profile() { + @SharedReader(.network) var network + XCTAssertEqual(network, .mainnet) + } + + func test_shared_reader_network_updates_when_gateway_switches() async throws { + let os = try await SargonOS.createdSharedBootingWith(bios: BIOS.insecure(), isEmulatingFreshInstall: true) + @SharedReader(.network) var network + let _ = try await os.changeCurrentGateway(to: .stokenet) + XCTAssertEqual(network, .stokenet) + } + + func test_shared_reader_accounts_switches_updates_when_gateway_switches() async throws { + let os = try await SargonOS.createdSharedBootingWith(bios: BIOS.insecure(), isEmulatingFreshInstall: true) + @SharedReader(.accountsForDisplay) var accountsForDisplay + let account = try await os.createAndSaveNewUnnamedMainnetAccount() + await Task.megaYield() + XCTAssertEqual(accountsForDisplay, [AccountForDisplay(account)]) + let _ = try await os.changeCurrentGateway(to: .stokenet) + await Task.megaYield() + XCTAssertEqual(accountsForDisplay, []) } } diff --git a/examples/iOS/Frontend/PlanbokApp.xcodeproj/project.pbxproj b/examples/iOS/Frontend/PlanbokApp.xcodeproj/project.pbxproj index 0609c21d2..eba745c6a 100644 --- a/examples/iOS/Frontend/PlanbokApp.xcodeproj/project.pbxproj +++ b/examples/iOS/Frontend/PlanbokApp.xcodeproj/project.pbxproj @@ -7,13 +7,10 @@ objects = { /* Begin PBXBuildFile section */ - 48601C3D2BB869270032792E /* Planbok in Frameworks */ = {isa = PBXBuildFile; productRef = 48601C3C2BB869270032792E /* Planbok */; }; - 48C8EF4C2B7DEA0300EF7F97 /* Planbok in Frameworks */ = {isa = PBXBuildFile; productRef = 48C8EF4B2B7DEA0300EF7F97 /* Planbok */; }; + 484ED3C72BE407DA00E5EF13 /* Planbok in Frameworks */ = {isa = PBXBuildFile; productRef = 484ED3C62BE407DA00E5EF13 /* Planbok */; }; 48F0A6CD2B7D42D400929DC2 /* PlanbokApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F0A6CC2B7D42D400929DC2 /* PlanbokApp.swift */; }; 48F0A6D12B7D42D500929DC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 48F0A6D02B7D42D500929DC2 /* Assets.xcassets */; }; 48F0A6D52B7D42D500929DC2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 48F0A6D42B7D42D500929DC2 /* Preview Assets.xcassets */; }; - E6EF0F482B7FA6CC00889B69 /* Sargon in Frameworks */ = {isa = PBXBuildFile; productRef = E6EF0F472B7FA6CC00889B69 /* Sargon */; }; - E6EF0F4B2B7FA6E500889B69 /* Planbok in Frameworks */ = {isa = PBXBuildFile; productRef = E6EF0F4A2B7FA6E500889B69 /* Planbok */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -29,21 +26,26 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E6EF0F4B2B7FA6E500889B69 /* Planbok in Frameworks */, - E6EF0F482B7FA6CC00889B69 /* Sargon in Frameworks */, - 48601C3D2BB869270032792E /* Planbok in Frameworks */, - 48C8EF4C2B7DEA0300EF7F97 /* Planbok in Frameworks */, + 484ED3C72BE407DA00E5EF13 /* Planbok in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 484ED3C52BE407DA00E5EF13 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; 48F0A6C02B7D42D400929DC2 = { isa = PBXGroup; children = ( 48F0A6CB2B7D42D400929DC2 /* PlanbokApp */, 48F0A6CA2B7D42D400929DC2 /* Products */, + 484ED3C52BE407DA00E5EF13 /* Frameworks */, ); sourceTree = ""; }; @@ -91,10 +93,7 @@ ); name = PlanbokApp; packageProductDependencies = ( - 48C8EF4B2B7DEA0300EF7F97 /* Planbok */, - E6EF0F472B7FA6CC00889B69 /* Sargon */, - E6EF0F4A2B7FA6E500889B69 /* Planbok */, - 48601C3C2BB869270032792E /* Planbok */, + 484ED3C62BE407DA00E5EF13 /* Planbok */, ); productName = Planbok; productReference = 48F0A6C92B7D42D400929DC2 /* PlanbokApp.app */; @@ -394,19 +393,7 @@ /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 48601C3C2BB869270032792E /* Planbok */ = { - isa = XCSwiftPackageProductDependency; - productName = Planbok; - }; - 48C8EF4B2B7DEA0300EF7F97 /* Planbok */ = { - isa = XCSwiftPackageProductDependency; - productName = Planbok; - }; - E6EF0F472B7FA6CC00889B69 /* Sargon */ = { - isa = XCSwiftPackageProductDependency; - productName = Sargon; - }; - E6EF0F4A2B7FA6E500889B69 /* Planbok */ = { + 484ED3C62BE407DA00E5EF13 /* Planbok */ = { isa = XCSwiftPackageProductDependency; productName = Planbok; }; diff --git a/examples/iOS/Frontend/PlanbokApp.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/examples/iOS/Frontend/PlanbokApp.xcodeproj/xcshareddata/xcschemes/App.xcscheme index f5b0870d9..5e98958af 100644 --- a/examples/iOS/Frontend/PlanbokApp.xcodeproj/xcshareddata/xcschemes/App.xcscheme +++ b/examples/iOS/Frontend/PlanbokApp.xcodeproj/xcshareddata/xcschemes/App.xcscheme @@ -27,8 +27,13 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - shouldAutocreateTestPlan = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + diff --git a/examples/iOS/Planbok.xcworkspace/xcshareddata/swiftpm/Package.resolved b/examples/iOS/Planbok.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4cd6d249f..84a136e2b 100644 --- a/examples/iOS/Planbok.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/examples/iOS/Planbok.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "asyncextensions", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sideeffect-io/AsyncExtensions", + "state" : { + "revision" : "1f0729e4f1f6c7166acfac3cec43b3cbe83be0e6", + "version" : "0.5.2" + } + }, { "identity" : "combine-schedulers", "kind" : "remoteSourceControl", @@ -23,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "551150d5e60e3be78972607d89cd69069cca3e7c", - "version" : "1.2.4" + "revision" : "8d712376c99fc0267aa0e41fea732babe365270a", + "version" : "1.3.3" } }, { @@ -50,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture", "state" : { - "revision" : "cf967a28a8605629559533320d604168d733fc9c", - "version" : "1.8.0" + "revision" : "433a23118f739078644ebeb4009e23d307af694a", + "version" : "1.10.4" } }, { @@ -68,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "6ea3b1b6a4957806d72030a32360d4bcb155a0d2", - "version" : "1.2.0" + "revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c", + "version" : "1.3.0" } }, { @@ -77,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "09e49dd46932adfe80ce672b4b3772d79ee6c21a", - "version" : "1.2.1" + "revision" : "350e1e119babe8525f9bd155b76640a5de270184", + "version" : "1.3.0" } }, { @@ -86,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/tgrapperon/swift-dependencies-additions", "state" : { - "revision" : "02e7b1801a96828049fe4d3e8bdc5e608ef5ffbc", - "version" : "1.0.1" + "revision" : "406ed2d853eb0e377173660f0965591ea7d31207", + "version" : "1.0.2" } }, { @@ -104,8 +113,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-perception", "state" : { - "revision" : "42240120b2a8797595433288ab4118f8042214c3", - "version" : "1.1.1" + "revision" : "8e8ca36c02abc7775a3ab81f9468c0df5fe22c2c", + "version" : "1.1.7" } }, { @@ -140,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "b58e6627149808b40634c4552fcf2f44d0b3ca87", - "version" : "1.1.0" + "revision" : "6f30bdba373bbd7fbfe241dddd732651f2fbd1e2", + "version" : "1.1.2" } } ], diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/antenna/SargonNetworkAntenna.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/antenna/SargonNetworkAntenna.kt index 0ad827ec7..2ffc75d32 100644 --- a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/antenna/SargonNetworkAntenna.kt +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/antenna/SargonNetworkAntenna.kt @@ -3,9 +3,9 @@ package com.radixdlt.sargon.antenna import com.radixdlt.sargon.CommonException -import com.radixdlt.sargon.NetworkAntenna import com.radixdlt.sargon.NetworkRequest import com.radixdlt.sargon.NetworkResponse +import com.radixdlt.sargon.NetworkingDriver import com.radixdlt.sargon.annotation.KoverIgnore import com.radixdlt.sargon.extensions.toBagOfBytes import com.radixdlt.sargon.extensions.toHttpMethod @@ -19,7 +19,7 @@ import okhttp3.executeAsync class SargonNetworkAntenna( private val client: OkHttpClient -) : NetworkAntenna { +) : NetworkingDriver { override suspend fun executeNetworkRequest(request: NetworkRequest): NetworkResponse = runCatching { val mediaType = request.headers.extractMediaType() diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/FactorSource.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/FactorSource.kt index 258ac6fae..f95aecc22 100644 --- a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/FactorSource.kt +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/FactorSource.kt @@ -1,6 +1,7 @@ package com.radixdlt.sargon.extensions import com.radixdlt.sargon.DeviceFactorSource +import com.radixdlt.sargon.DeviceInfo import com.radixdlt.sargon.FactorSource import com.radixdlt.sargon.FactorSourceId import com.radixdlt.sargon.FactorSourceKind @@ -29,19 +30,21 @@ fun DeviceFactorSource.asGeneral() = FactorSource.Device(value = this) fun LedgerHardwareWalletFactorSource.asGeneral() = FactorSource.Ledger(value = this) fun FactorSource.Device.Companion.olympia( - mnemonicWithPassphrase: MnemonicWithPassphrase + mnemonicWithPassphrase: MnemonicWithPassphrase, + deviceInfo: DeviceInfo ) = newDeviceFactorSourceOlympia( mnemonicWithPassphrase = mnemonicWithPassphrase, - walletClientModel = WalletClientModel.ANDROID + deviceInfo = deviceInfo ).asGeneral() fun FactorSource.Device.Companion.babylon( isMain: Boolean, - mnemonicWithPassphrase: MnemonicWithPassphrase + mnemonicWithPassphrase: MnemonicWithPassphrase, + deviceInfo: DeviceInfo ) = newDeviceFactorSourceBabylon( isMain = isMain, mnemonicWithPassphrase = mnemonicWithPassphrase, - walletClientModel = WalletClientModel.ANDROID + deviceInfo = deviceInfo ).asGeneral() val FactorSource.Device.isMain: Boolean diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/NetworkMethod.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/NetworkMethod.kt index 1075bb1c6..9b2f02cf8 100644 --- a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/NetworkMethod.kt +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/NetworkMethod.kt @@ -1,8 +1,6 @@ package com.radixdlt.sargon.extensions import com.radixdlt.sargon.NetworkMethod +import com.radixdlt.sargon.networkMethodToString -fun NetworkMethod.toHttpMethod(): String = when (this) { - NetworkMethod.POST -> "POST" - NetworkMethod.GET -> "GET" -} \ No newline at end of file +fun NetworkMethod.toHttpMethod(): String = networkMethodToString(method = this) \ No newline at end of file diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/PrivateHierarchicalDeterministicFactorSource.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/PrivateHierarchicalDeterministicFactorSource.kt index 9ed1839b9..cc7d5d7f4 100644 --- a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/PrivateHierarchicalDeterministicFactorSource.kt +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/PrivateHierarchicalDeterministicFactorSource.kt @@ -1,5 +1,6 @@ package com.radixdlt.sargon.extensions +import com.radixdlt.sargon.DeviceInfo import com.radixdlt.sargon.MnemonicWithPassphrase import com.radixdlt.sargon.NonEmptyMax32Bytes import com.radixdlt.sargon.PrivateHierarchicalDeterministicFactorSource @@ -12,27 +13,29 @@ import com.radixdlt.sargon.newPrivateHdFactorSourceOlympiaFromMnemonicWithPassph fun PrivateHierarchicalDeterministicFactorSource.Companion.init( isMainBDFS: Boolean, entropy: NonEmptyMax32Bytes, - walletClientModel: WalletClientModel + deviceInfo: DeviceInfo ) = newPrivateHdFactorSourceBabylon( isMain = isMainBDFS, entropy = entropy, - walletClientModel = walletClientModel + deviceInfo = deviceInfo ) fun PrivateHierarchicalDeterministicFactorSource.Companion.olympia( - mnemonicWithPassphrase: MnemonicWithPassphrase + mnemonicWithPassphrase: MnemonicWithPassphrase, + deviceInfo: DeviceInfo ) = newPrivateHdFactorSourceOlympiaFromMnemonicWithPassphrase( mnemonicWithPassphrase = mnemonicWithPassphrase, - walletClientModel = WalletClientModel.ANDROID + deviceInfo = deviceInfo ) fun PrivateHierarchicalDeterministicFactorSource.Companion.babylon( isMain: Boolean, - mnemonicWithPassphrase: MnemonicWithPassphrase + mnemonicWithPassphrase: MnemonicWithPassphrase, + deviceInfo: DeviceInfo ) = newPrivateHdFactorSourceBabylonFromMnemonicWithPassphrase( isMain = isMain, mnemonicWithPassphrase = mnemonicWithPassphrase, - walletClientModel = WalletClientModel.ANDROID + deviceInfo = deviceInfo ) diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/Profile.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/Profile.kt index eb72b8054..7b4021e3a 100644 --- a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/Profile.kt +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/Profile.kt @@ -1,5 +1,6 @@ package com.radixdlt.sargon.extensions +import com.radixdlt.sargon.DeviceInfo import com.radixdlt.sargon.FactorSource import com.radixdlt.sargon.Profile import com.radixdlt.sargon.ProfileFileContents @@ -14,10 +15,10 @@ import com.radixdlt.sargon.profileToJsonBytes fun Profile.Companion.init( deviceFactorSource: FactorSource.Device, - creatingDeviceName: String + deviceInfo: DeviceInfo ) = newProfile( deviceFactorSource = deviceFactorSource.value, - creatingDeviceName = creatingDeviceName + deviceInfo = deviceInfo ) fun Profile.Companion.analyzeContentsOfFile(contents: String): ProfileFileContents = diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/DeviceFactorSourceSample.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/DeviceFactorSourceSample.kt new file mode 100644 index 000000000..27ab7d549 --- /dev/null +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/DeviceFactorSourceSample.kt @@ -0,0 +1,15 @@ +package com.radixdlt.sargon.samples + +import com.radixdlt.sargon.annotation.UsesSampleValues +import com.radixdlt.sargon.DeviceFactorSource +import com.radixdlt.sargon.newDeviceFactorSourceSample +import com.radixdlt.sargon.newDeviceFactorSourceSampleOther + +@UsesSampleValues +val DeviceFactorSource.Companion.sample: Sample + get() = object : Sample { + + override fun invoke(): DeviceFactorSource = newDeviceFactorSourceSample() + + override fun other(): DeviceFactorSource = newDeviceFactorSourceSampleOther() + } \ No newline at end of file diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/DeviceInfoSample.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/DeviceInfoSample.kt new file mode 100644 index 000000000..2ab81e5d8 --- /dev/null +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/DeviceInfoSample.kt @@ -0,0 +1,15 @@ +package com.radixdlt.sargon.samples + +import com.radixdlt.sargon.annotation.UsesSampleValues +import com.radixdlt.sargon.DeviceInfo +import com.radixdlt.sargon.newDeviceInfoSample +import com.radixdlt.sargon.newDeviceInfoSampleOther + +@UsesSampleValues +val DeviceInfo.Companion.sample: Sample + get() = object : Sample { + + override fun invoke(): DeviceInfo = newDeviceInfoSample() + + override fun other(): DeviceInfo = newDeviceInfoSampleOther() + } \ No newline at end of file diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/LedgerHardwareWalletFactorSourceSample.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/LedgerHardwareWalletFactorSourceSample.kt new file mode 100644 index 000000000..535bead4d --- /dev/null +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/LedgerHardwareWalletFactorSourceSample.kt @@ -0,0 +1,15 @@ +package com.radixdlt.sargon.samples + +import com.radixdlt.sargon.annotation.UsesSampleValues +import com.radixdlt.sargon.LedgerHardwareWalletFactorSource +import com.radixdlt.sargon.newLedgerHardwareWalletFactorSourceSample +import com.radixdlt.sargon.newLedgerHardwareWalletFactorSourceSampleOther + +@UsesSampleValues +val LedgerHardwareWalletFactorSource.Companion.sample: Sample + get() = object : Sample { + + override fun invoke(): LedgerHardwareWalletFactorSource = newLedgerHardwareWalletFactorSourceSample() + + override fun other(): LedgerHardwareWalletFactorSource = newLedgerHardwareWalletFactorSourceSampleOther() + } \ No newline at end of file diff --git a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/EphemeralKeystore.kt b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/EphemeralKeystore.kt deleted file mode 100644 index aa6a7d7dd..000000000 --- a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/EphemeralKeystore.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.radixdlt.sargon - -import com.radixdlt.sargon.extensions.identifier - -internal object EphemeralKeystore : SecureStorage { - private val storage: MutableMap = mutableMapOf() - - override fun loadData(key: SecureStorageKey): ByteArray? = storage[key.identifier] - - override fun saveData(key: SecureStorageKey, data: ByteArray) { - storage[key.identifier] = data - } - - override fun deleteDataForKey(key: SecureStorageKey) { - storage.remove(key = key.identifier) - } - - fun isEmpty() = storage.isEmpty() - - fun contains(value: String): Boolean { - return storage.any { entry -> entry.value.decodeToString().contains(value) } - } -} \ No newline at end of file diff --git a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/FactorSourceTest.kt b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/FactorSourceTest.kt index a1ae3537f..d32c6760e 100644 --- a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/FactorSourceTest.kt +++ b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/FactorSourceTest.kt @@ -1,5 +1,6 @@ package com.radixdlt.sargon +import android.bluetooth.BluetoothClass.Device import com.radixdlt.sargon.extensions.babylon import com.radixdlt.sargon.extensions.isMain import com.radixdlt.sargon.extensions.kind @@ -36,7 +37,8 @@ class FactorSourceTest : SampleTestable { assertTrue( FactorSource.Device.babylon( isMain = true, - mnemonicWithPassphrase = MnemonicWithPassphrase.sample() + mnemonicWithPassphrase = MnemonicWithPassphrase.sample(), + deviceInfo = DeviceInfo.sample() ).isMain ) } @@ -46,7 +48,8 @@ class FactorSourceTest : SampleTestable { assertFalse( FactorSource.Device.babylon( isMain = false, - mnemonicWithPassphrase = MnemonicWithPassphrase.sample() + mnemonicWithPassphrase = MnemonicWithPassphrase.sample(), + deviceInfo = DeviceInfo.sample() ).isMain ) } @@ -55,7 +58,8 @@ class FactorSourceTest : SampleTestable { fun testNewBabylon() { val factorSource = FactorSource.Device.babylon( isMain = false, - mnemonicWithPassphrase = MnemonicWithPassphrase.sample() + mnemonicWithPassphrase = MnemonicWithPassphrase.sample(), + deviceInfo = DeviceInfo.sample() ) assertTrue(factorSource.supportsBabylon) assertFalse(factorSource.supportsOlympia) @@ -64,7 +68,8 @@ class FactorSourceTest : SampleTestable { @Test fun testNewOlympia() { val factorSource = FactorSource.Device.olympia( - mnemonicWithPassphrase = MnemonicWithPassphrase.sample() + mnemonicWithPassphrase = MnemonicWithPassphrase.sample(), + deviceInfo = DeviceInfo.sample() ) assertTrue(factorSource.supportsOlympia) assertFalse(factorSource.supportsBabylon) diff --git a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/FactorSourcesTest.kt b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/FactorSourcesTest.kt index 432a989dd..047a83324 100644 --- a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/FactorSourcesTest.kt +++ b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/FactorSourcesTest.kt @@ -66,26 +66,7 @@ internal class FactorSourcesTest : IdentifiedArrayTest { val deviceInfo = DeviceInfo( id = UUID.randomUUID(), date = Timestamp.now(), - description = "Unit test" + description = DeviceInfoDescription(name = "My Precious", model = "Samsung Galaxy"), + systemVersion = "0.0.1", + hostAppVersion = "1.6.0", + hostVendor = "Samsung" ) val header = Header.init(creatingDevice = deviceInfo) diff --git a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/PrivateHierarchicalDeterministicFactorSourceTest.kt b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/PrivateHierarchicalDeterministicFactorSourceTest.kt index d68377a56..37eb80efc 100644 --- a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/PrivateHierarchicalDeterministicFactorSourceTest.kt +++ b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/PrivateHierarchicalDeterministicFactorSourceTest.kt @@ -17,7 +17,8 @@ class PrivateHierarchicalDeterministicFactorSourceTest { fun testNewBabylon() { val sut = PrivateHierarchicalDeterministicFactorSource.babylon( isMain = true, - mnemonicWithPassphrase = MnemonicWithPassphrase.sample() + mnemonicWithPassphrase = MnemonicWithPassphrase.sample(), + deviceInfo = DeviceInfo.sample() ) Assertions.assertTrue(sut.factorSource.asGeneral().supportsBabylon) } @@ -25,7 +26,8 @@ class PrivateHierarchicalDeterministicFactorSourceTest { @Test fun testNewOlympia() { val sut = PrivateHierarchicalDeterministicFactorSource.olympia( - mnemonicWithPassphrase = MnemonicWithPassphrase.sample() + mnemonicWithPassphrase = MnemonicWithPassphrase.sample(), + deviceInfo = DeviceInfo.sample() ) Assertions.assertTrue(sut.factorSource.asGeneral().supportsOlympia) } @@ -36,14 +38,16 @@ class PrivateHierarchicalDeterministicFactorSourceTest { FactorSourceKind.DEVICE, PrivateHierarchicalDeterministicFactorSource.babylon( isMain = true, - mnemonicWithPassphrase = MnemonicWithPassphrase.sample() + mnemonicWithPassphrase = MnemonicWithPassphrase.sample(), + deviceInfo = DeviceInfo.sample() ).factorSource.kind ) assertEquals( FactorSourceKind.DEVICE, PrivateHierarchicalDeterministicFactorSource.olympia( - mnemonicWithPassphrase = MnemonicWithPassphrase.sample() + mnemonicWithPassphrase = MnemonicWithPassphrase.sample(), + deviceInfo = DeviceInfo.sample() ).factorSource.kind ) } diff --git a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/ProfileTest.kt b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/ProfileTest.kt index fcd987666..4e2c1211b 100644 --- a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/ProfileTest.kt +++ b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/ProfileTest.kt @@ -28,15 +28,15 @@ class ProfileTest : SampleTestable { val hdFactorSource = PrivateHierarchicalDeterministicFactorSource.init( isMainBDFS = true, entropy = NonEmptyMax32Bytes(bagOfBytes = randomBagOfBytes(byteCount = 32)), - walletClientModel = WalletClientModel.ANDROID + deviceInfo = DeviceInfo.sample.other() ) val profile = Profile.init( deviceFactorSource = hdFactorSource.factorSource.asGeneral(), - creatingDeviceName = "Unit tests" + deviceInfo = DeviceInfo.sample.other() ) - assertEquals("Unit tests - Android", profile.header.creatingDevice.description) + assertEquals("Android", profile.header.creatingDevice.description.name) } @Test diff --git a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/WalletTest.kt b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/WalletTest.kt deleted file mode 100644 index 3a3233246..000000000 --- a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/WalletTest.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.radixdlt.sargon - -import com.radixdlt.sargon.extensions.init -import com.radixdlt.sargon.extensions.toBagOfBytes -import com.radixdlt.sargon.samples.sample -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertNotEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import kotlin.random.Random - -class WalletTest { - - @Test - fun test() { - println("🚀 Test Wallet in Kotlin start") - - val storage = EphemeralKeystore - assertTrue(storage.isEmpty()) - - println("🔮 GENERATING NEW WALLET") - val wallet = Wallet.with(entropy = ByteArray(32) { 0xFF.toByte() }, secureStorage = storage) - - assertTrue( - storage.contains( - value = - "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote" - ) - ) - println("✨ SUCCESSFULLY GENERATED NEW WALLET ✅") - - println("🔮 Creating first account on mainnet") - val initialNameOfFirstAccount = "Alice" - // Not created any account yet... - assertFalse(storage.contains(value = initialNameOfFirstAccount)) - assertTrue(wallet.profile().networks.isEmpty()) - var main0 = wallet.createAndSaveNewAccount( - networkId = NetworkId.MAINNET, - name = DisplayName.init(validating = initialNameOfFirstAccount) - ) - assertEquals(NetworkId.MAINNET, main0.networkId) - assertEquals(1, wallet.profile().networks.size) - assertEquals(1, wallet.profile().networks[0].accounts.size) - assertEquals( - initialNameOfFirstAccount, - wallet.profile().networks[0].accounts[0].displayName.value - ) - assertTrue(storage.contains(value = initialNameOfFirstAccount)) - println("✨ Successfully created first account ✅") - - println("🔮 Update account using `update_account`") - var updatedNameOfFirstAccount = "Stella" - main0.displayName = DisplayName.init(validating = updatedNameOfFirstAccount) - main0.appearanceId = AppearanceId.sample.other() - val main0Updated = wallet.updateAccount(to = main0) - assertEquals(main0, main0Updated) - assertEquals( - updatedNameOfFirstAccount, - wallet.profile().networks[0].accounts[0].displayName.value - ) - assertEquals( - AppearanceId.sample.other(), - wallet.profile().networks[0].accounts[0].appearanceId - ) - assertTrue(storage.contains(value = updatedNameOfFirstAccount)) - println("✨ Successfully updated first account using `update_account` ✅") - - println("🔮 Renaming account using changeNameOfAccount") - updatedNameOfFirstAccount = "Satoshi" - main0 = wallet.changeNameOfAccount( - address = main0.address, - to = DisplayName.init(validating = updatedNameOfFirstAccount) - ) - assertEquals( - updatedNameOfFirstAccount, - wallet.profile().networks[0].accounts[0].displayName.value - ) - assertTrue(storage.contains(value = updatedNameOfFirstAccount)) - println("✨ Successfully renamed first account using changeNameOfAccount ✅") - - println("🔮 Creating second mainnet account") - val main1 = wallet.createAndSaveNewAccount( - networkId = NetworkId.MAINNET, - name = DisplayName.init(validating = "Bob") - ) - assertNotEquals(main0.address, main1.address) - assertEquals(main0.networkId, main1.networkId) - assertEquals(1, wallet.profile().networks.size) - assertEquals(listOf(main0, main1), wallet.profile().networks[0].accounts) - - println("🔮 Creating first testnet account") - val testnetAccountName = "Hello Radix Account!" - val test0 = wallet.createAndSaveNewAccount( - networkId = NetworkId.STOKENET, - name = DisplayName.init(validating = testnetAccountName) - ) - assertEquals(2, wallet.profile().networks.size) - assertEquals(listOf(test0), wallet.profile().networks[1].accounts) - assertEquals(testnetAccountName, wallet.profile().networks[1].accounts[0].displayName.value) - assertEquals(NetworkId.STOKENET, wallet.profile().networks[1].accounts[0].networkId) - assertTrue(storage.contains(value = testnetAccountName)) - println("✨ Successfully created first testnet account ✅") - - println("✅ Test Wallet in Kotlin completed ") - } - - val Wallet.Companion.defaultPhoneName: String - get() = "Android Phone" - - fun Wallet.Companion.with( - entropy: ByteArray = ByteArray(32).apply { Random.nextBytes(this) }, - phoneName: String = Wallet.Companion.defaultPhoneName, - secureStorage: SecureStorage - ): Wallet { - return Wallet.byCreatingNewProfileAndSecretsWithEntropy( - entropy = NonEmptyMax32Bytes(bagOfBytes = entropy.toBagOfBytes()), - walletClientModel = WalletClientModel.ANDROID, - walletClientName = phoneName, - secureStorage = secureStorage - ) - } - -} \ No newline at end of file diff --git a/scripts/ios/build-sargon.sh b/scripts/ios/build-sargon.sh index 8faee8c11..a5473fc0d 100755 --- a/scripts/ios/build-sargon.sh +++ b/scripts/ios/build-sargon.sh @@ -43,11 +43,10 @@ generate_ffi() { local TARGET_FOR_DYLIB_PATH="aarch64-apple-darwin" else local TARGET_FOR_DYLIB_PATH="aarch64-apple-ios" - fi - cargo run --features build-binary --bin sargon-bindgen generate --library target/$TARGET_FOR_DYLIB_PATH/release/lib$1.dylib --language swift --out-dir target/uniffi-xcframework-staging + fi + cargo run -p uniffi-bindgen --bin sargon-bindgen generate --library target/$TARGET_FOR_DYLIB_PATH/release/lib$1.dylib --language swift --out-dir target/uniffi-xcframework-staging mkdir -p apple/Sources/UniFFI/ mv target/uniffi-xcframework-staging/*.swift apple/Sources/UniFFI/ - mv target/uniffi-xcframework-staging/$1FFI.modulemap target/uniffi-xcframework-staging/module.modulemap # Convention requires this have a specific name } @@ -96,7 +95,7 @@ else echo "📦 Start of '$me' (see: '$DIR/$me')" fi -cd "$DIR" +cd "$DIR" cd "../../" # go to parent of parent, which is project root. @@ -119,4 +118,4 @@ echo "📦 ✅ End of '$me', output" # This print MUST be the last print. # The path is read by `release.sh` script. # This is probably terrible... but I'm a Rust/Swift dev, not a bash script dev... -echo "$OUTPUT_OF_BUILD" \ No newline at end of file +echo "$OUTPUT_OF_BUILD" diff --git a/scripts/ios/test.sh b/scripts/ios/test.sh index b4a018a5e..d2db87edb 100755 --- a/scripts/ios/test.sh +++ b/scripts/ios/test.sh @@ -5,6 +5,7 @@ set -u # By default we test with code coverage and display details (lines missed) +should_build=false export_code_cov=false testonly=false # if true, no code coverage will happen summary=false # if true, code coverage will only show summary, no details @@ -13,6 +14,10 @@ code_cov_report_file_path="" for arg in "$@" do case $arg in + --build) + should_build=true + shift # Remove --build from processing + ;; --codecov) export_code_cov=true code_cov_report_file_path="$2" @@ -44,9 +49,13 @@ echo "✨ PWD: $PWD" echo "✨ Ensure 'useLocalFramework' is set to 'true' in Package.swift" sh ./scripts/ios/ensure-is-local.sh || exit $? -echo "✨ Building Sargon..." -sh ./scripts/ios/build-sargon.sh --maconly || exit $? -echo "✨ Sargon built" +if $should_build; then + echo "✨ Building Sargon..." + sh ./scripts/ios/build-sargon.sh --maconly || exit $? + echo "✨ Sargon built" +else + echo "🙅‍♀️ Skip building, test (and coverage?) only" +fi echo "✨ Calling 'swift test'" if $testonly; then diff --git a/src/Package.swift b/src/Package.swift deleted file mode 100644 index 9a7fcd3ba..000000000 --- a/src/Package.swift +++ /dev/null @@ -1,13 +0,0 @@ -// swift-tools-version:5.9 - -// This is a HACKY workaround the fact that SPM does not allow for Package level exclusion -// of files/folders. SPM actually HAD support for it but it was removed in 2017, in PR -// https://github.com/apple/swift-package-manager/commit/cb69accf41da55386f9703308958aa49ca2a4c5f -// -// So instead we have to add an empty dummy Package.swift to each folder we wanna hide, as per: -// See: https://github.com/apple/swift-package-manager/issues/4460#issuecomment-1475025748 -// And: https://stackoverflow.com/questions/69382302/swift-package-how-to-exclude-files-in-root-git-directory-from-the-actual-swift/70990534#70990534 -// And: https://github.com/tuist/tuist/pull/2058 -import PackageDescription - -let package = Package() \ No newline at end of file diff --git a/src/bindgen/bin.rs b/src/bindgen/bin.rs deleted file mode 100644 index 1ac99ce1e..000000000 --- a/src/bindgen/bin.rs +++ /dev/null @@ -1,18 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -mod args; -mod bindgen_error; -mod post_process; -mod post_process_kotlin; -mod post_process_swift; - -use crate::post_process::*; - -fn main() { - println!("🔮 Running sargon-bindgen"); - uniffi::uniffi_bindgen_main(); - post_process(); - println!("🔮 Finished with sargon-bindgen ✅"); -} diff --git a/src/core/mod.rs b/src/core/mod.rs deleted file mode 100644 index 693d3bab9..000000000 --- a/src/core/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod assert_json; -mod error; -mod has_sample_values; -mod hash; -mod hash_uniffi_fn; -mod secure_random_bytes; -mod types; -mod unsafe_id_stepper; -mod utils; - -pub use assert_json::*; -pub use error::*; -pub use has_sample_values::*; -pub use hash::*; -pub use hash_uniffi_fn::*; -pub use secure_random_bytes::*; -pub use types::*; -pub use unsafe_id_stepper::*; -pub use utils::*; diff --git a/src/core/types/keys/is_private_key.rs b/src/core/types/keys/is_private_key.rs deleted file mode 100644 index 10ab8718f..000000000 --- a/src/core/types/keys/is_private_key.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::prelude::*; - -pub trait IsPrivateKey>: Sized { - type Signature; - - fn from_bytes(slice: &[u8]) -> Result; - - fn curve() -> SLIP10Curve; - - fn public_key(&self) -> P; - - fn sign(&self, msg_hash: &Hash) -> Self::Signature; - - fn sign_intent_hash(&self, intent_hash: &IntentHash) -> IntentSignature - where - (P, Self::Signature): Into, - { - let public_key: P = self.public_key(); - let signature = self.sign(&intent_hash.hash); - let tuple: SignatureWithPublicKey = (public_key, signature).into(); - tuple.into() - } - - fn notarize_hash( - &self, - signed_intent_hash: &SignedIntentHash, - ) -> NotarySignature - where - Self::Signature: Into, - { - self.sign(&signed_intent_hash.hash).into() - } -} diff --git a/src/core/types/secret_bytes.rs b/src/core/types/secret_bytes.rs deleted file mode 100644 index ceeef64e6..000000000 --- a/src/core/types/secret_bytes.rs +++ /dev/null @@ -1,180 +0,0 @@ -use crate::prelude::*; - -macro_rules! decl_secret_bytes { - ( - $( - #[doc = $expr: expr] - )* - $struct_name: ident, - $byte_count: literal - ) => { - paste! { - - #[derive(Zeroize, ZeroizeOnDrop, derive_more::Debug, derive_more::Display)] - #[debug("OBFUSCATED")] - #[display("OBFUSCATED")] - pub struct [< $struct_name SecretMagic >](Box<[u8; $byte_count]>); - - uniffi::custom_type!([< $struct_name SecretMagic >], BagOfBytes); - - impl TryFrom for [< $struct_name SecretMagic >] { - type Error = CommonError; - fn try_from(value: BagOfBytes) -> Result { - let fixed_size: &[u8; $byte_count] = value.as_ref().try_into().map_err(|_| CommonError::InvalidByteCount { expected: $byte_count as u64, found: value.len() as u64 })?; - Ok(Self(Box::new(*fixed_size))) - } - } - - impl $crate::UniffiCustomTypeConverter for [< $struct_name SecretMagic >] { - type Builtin = BagOfBytes; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Self::try_from(val).map_err(|e| e.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { - BagOfBytes::from(obj.0.as_slice()) - } - } - - $( - #[doc = $expr] - )* - #[derive(Zeroize, derive_more::Debug, derive_more::Display, uniffi::Record)] - #[debug("OBFUSCATED")] - #[display("OBFUSCATED")] - pub struct $struct_name { - secret_magic: [< $struct_name SecretMagic >] - } - - #[uniffi::export] - pub fn [< new_ $struct_name:snake _from_bytes >](bytes: BagOfBytes) -> Result<$struct_name> { - [< $struct_name SecretMagic >]::try_from(bytes) - .map(|secret_magic| $struct_name { secret_magic }) - } - - impl $struct_name { - pub fn to_bytes(&self) -> &[u8] { - &self.secret_magic.0.as_slice() - } - } - - impl HasSampleValues for $struct_name { - fn sample() -> Self { - Self { secret_magic: [< $struct_name SecretMagic >](Box::new([0xab; $byte_count])) } - } - - fn sample_other() -> Self { - Self { secret_magic: [< $struct_name SecretMagic >](Box::new([0xde; $byte_count])) } - } - } - - #[uniffi::export] - pub fn [< new_ $struct_name:snake _sample >]() -> $struct_name { - $struct_name::sample() - } - - #[uniffi::export] - pub fn [< new_ $struct_name:snake _sample_other >]() -> $struct_name { - $struct_name::sample_other() - } - - #[uniffi::export] - pub fn [< $struct_name:snake _to_bytes >](bytes: &$struct_name) -> BagOfBytes { - BagOfBytes::from(bytes.to_bytes()) - } - - impl $struct_name { - pub const LENGTH: usize = $byte_count; - - pub fn new(bytes: [u8; Self::LENGTH]) -> Self { - Self { - secret_magic: [< $struct_name SecretMagic >](Box::new(bytes)) - } - } - - #[allow(unused)] - pub(crate) fn is_zeroized(&self) -> bool { - *self.secret_magic.0 == [0; Self::LENGTH] - } - } - - #[cfg(test)] - mod [< uniffi_ $struct_name:snake tests >] { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = $struct_name; - - #[test] - fn test_from_bytes() { - let too_few_bytes = BagOfBytes::from_str("dead").unwrap(); - assert!([< new_ $struct_name:snake _from_bytes >](too_few_bytes).is_err()); - } - - #[test] - fn get_bytes() { - let sut = SUT::sample(); - let bytes = [< $struct_name:snake _to_bytes >](&sut); - assert_eq!(bytes.as_ref(), [0xab; $byte_count]); - } - - #[test] - fn zeroize_sample() { - let mut sut = [< new_ $struct_name:snake _sample >](); - assert!(!sut.is_zeroized()); - sut.zeroize(); - assert!(sut.is_zeroized()); - } - - #[test] - fn zeroize_sample_other() { - let mut sut = [< new_ $struct_name:snake _sample_other >](); - assert!(!sut.is_zeroized()); - sut.zeroize(); - assert!(sut.is_zeroized()); - } - - #[test] - fn test_to_bytes() { - let sut = [< new_ $struct_name:snake _sample >](); - assert_eq!( - sut.secret_magic.0.as_slice(), - sut.to_bytes() - ) - } - } - - #[cfg(test)] - mod [< $struct_name:snake tests >] { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = $struct_name; - - #[test] - fn zeroize() { - let mut sut = SUT::sample(); - assert!(!sut.is_zeroized()); - sut.zeroize(); - assert!(sut.is_zeroized()); - } - - #[test] - fn debug_obfuscates_secret() { - let sut = SUT::sample_other(); - assert_eq!(format!("{:?}", sut), "OBFUSCATED"); - } - - #[test] - fn display_obfuscates_secret() { - let sut = SUT::sample_other(); - assert_eq!(format!("{}", sut), "OBFUSCATED"); - } - } - - } - }; -} - -pub(crate) use decl_secret_bytes; diff --git a/src/core/utils/factory.rs b/src/core/utils/factory.rs deleted file mode 100644 index b414d6b77..000000000 --- a/src/core/utils/factory.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::prelude::*; - -pub fn now() -> Timestamp { - Timestamp::now_utc() -} - -pub fn id() -> Uuid { - Uuid::new_v4() -} - -pub fn profile_id() -> ProfileID { - ProfileID(id()) -} - -pub fn iso8601(dt: &Timestamp) -> String { - let (h, m, s) = dt.as_hms(); - format!("{} {:02}:{:02}:{:02}", date(dt), h, m, s) -} - -pub fn date(dt: &Timestamp) -> String { - dt.date().to_string() -} - -#[cfg(test)] -mod tests { - use crate::prelude::*; - - #[test] - fn date_now() { - let d0 = now(); - let mut d1 = now(); - for _ in 0..10 { - d1 = now(); - } - assert!(d1 > d0); - } - - #[test] - fn id_unique() { - let n = 100; - let set = (0..n).map(|_| id()).collect::>(); - assert_eq!(set.len(), n); - } - - #[test] - fn date_str() { - assert_eq!(date(&Timestamp::UNIX_EPOCH), "1970-01-01"); - assert_eq!(iso8601(&Timestamp::UNIX_EPOCH), "1970-01-01 00:00:00"); - } -} diff --git a/src/gateway_api/endpoints/mod.rs b/src/gateway_api/endpoints/mod.rs deleted file mode 100644 index 9904afeb0..000000000 --- a/src/gateway_api/endpoints/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod state_endpoints; -mod transaction_endpoints; - -pub use state_endpoints::*; -pub use transaction_endpoints::*; diff --git a/src/gateway_api/mod.rs b/src/gateway_api/mod.rs deleted file mode 100644 index 25d1a92f5..000000000 --- a/src/gateway_api/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod client; -mod endpoints; -mod methods; -mod models; - -pub use client::*; -pub use endpoints::*; -pub use methods::*; -pub use models::*; diff --git a/src/gateway_api/models/types/request/mod.rs b/src/gateway_api/models/types/request/mod.rs deleted file mode 100644 index 898eff12f..000000000 --- a/src/gateway_api/models/types/request/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod gw_public_key; -mod state; -mod transaction; - -pub use gw_public_key::*; -pub use state::*; -pub use transaction::*; diff --git a/src/gateway_api/models/types/request/state/entity/mod.rs b/src/gateway_api/models/types/request/state/entity/mod.rs deleted file mode 100644 index c2ca873c5..000000000 --- a/src/gateway_api/models/types/request/state/entity/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod state_entity_details; - -pub use state_entity_details::*; diff --git a/src/gateway_api/models/types/request/state/mod.rs b/src/gateway_api/models/types/request/state/mod.rs deleted file mode 100644 index 135a9f2a7..000000000 --- a/src/gateway_api/models/types/request/state/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod entity; - -pub use entity::*; diff --git a/src/gateway_api/models/types/request/transaction/mod.rs b/src/gateway_api/models/types/request/transaction/mod.rs deleted file mode 100644 index 8a3a19527..000000000 --- a/src/gateway_api/models/types/request/transaction/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod preview; -mod submit; - -pub use preview::*; -pub use submit::*; diff --git a/src/gateway_api/models/types/request/transaction/preview/mod.rs b/src/gateway_api/models/types/request/transaction/preview/mod.rs deleted file mode 100644 index 77d66a4f0..000000000 --- a/src/gateway_api/models/types/request/transaction/preview/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod request_flags; -mod transaction_preview; - -pub use request_flags::*; -pub use transaction_preview::*; diff --git a/src/gateway_api/models/types/request/transaction/submit/mod.rs b/src/gateway_api/models/types/request/transaction/submit/mod.rs deleted file mode 100644 index 9492d61e5..000000000 --- a/src/gateway_api/models/types/request/transaction/submit/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod transaction_submit; - -pub use transaction_submit::*; diff --git a/src/gateway_api/models/types/response/mod.rs b/src/gateway_api/models/types/response/mod.rs deleted file mode 100644 index a1fedc73b..000000000 --- a/src/gateway_api/models/types/response/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod ledger_state; - -mod state; -mod transaction; - -pub use ledger_state::*; - -pub use state::*; -pub use transaction::*; diff --git a/src/gateway_api/models/types/response/state/entity/details/fungible/mod.rs b/src/gateway_api/models/types/response/state/entity/details/fungible/mod.rs deleted file mode 100644 index aaed90fec..000000000 --- a/src/gateway_api/models/types/response/state/entity/details/fungible/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod collection; -mod collection_item; -mod collection_item_global; - -pub use collection::*; -pub use collection_item::*; -pub use collection_item_global::*; diff --git a/src/gateway_api/models/types/response/state/entity/details/mod.rs b/src/gateway_api/models/types/response/state/entity/details/mod.rs deleted file mode 100644 index febab0e68..000000000 --- a/src/gateway_api/models/types/response/state/entity/details/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod fungible; -mod state_entity_details_response; -mod state_entity_details_response_item; - -pub use fungible::*; -pub use state_entity_details_response::*; -pub use state_entity_details_response_item::*; diff --git a/src/gateway_api/models/types/response/state/entity/mod.rs b/src/gateway_api/models/types/response/state/entity/mod.rs deleted file mode 100644 index 973598716..000000000 --- a/src/gateway_api/models/types/response/state/entity/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod details; -pub use details::*; diff --git a/src/gateway_api/models/types/response/state/mod.rs b/src/gateway_api/models/types/response/state/mod.rs deleted file mode 100644 index 135a9f2a7..000000000 --- a/src/gateway_api/models/types/response/state/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod entity; - -pub use entity::*; diff --git a/src/gateway_api/models/types/response/transaction/construction/mod.rs b/src/gateway_api/models/types/response/transaction/construction/mod.rs deleted file mode 100644 index ff9fcd584..000000000 --- a/src/gateway_api/models/types/response/transaction/construction/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod transaction_construction_response; - -pub use transaction_construction_response::*; diff --git a/src/gateway_api/models/types/response/transaction/preview/mod.rs b/src/gateway_api/models/types/response/transaction/preview/mod.rs deleted file mode 100644 index 3b23abab6..000000000 --- a/src/gateway_api/models/types/response/transaction/preview/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod logs_inner; -mod transaction_preview_response; - -pub use logs_inner::*; -pub use transaction_preview_response::*; diff --git a/src/gateway_api/models/types/response/transaction/submit/mod.rs b/src/gateway_api/models/types/response/transaction/submit/mod.rs deleted file mode 100644 index a66c097b8..000000000 --- a/src/gateway_api/models/types/response/transaction/submit/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod submit; - -pub use submit::*; diff --git a/src/hierarchical_deterministic/.DS_Store b/src/hierarchical_deterministic/.DS_Store deleted file mode 100644 index c0b35c89d..000000000 Binary files a/src/hierarchical_deterministic/.DS_Store and /dev/null differ diff --git a/src/hierarchical_deterministic/bip39/bip39_entropy.rs b/src/hierarchical_deterministic/bip39/bip39_entropy.rs deleted file mode 100644 index fdb286a2f..000000000 --- a/src/hierarchical_deterministic/bip39/bip39_entropy.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::{decl_secret_bytes, prelude::*, UniffiCustomTypeConverter}; - -macro_rules! entropy_with_byte_counts { - ( - $( - #[doc = $expr: expr] - )* - $enum_name: ident: - $( - $byte_count: literal, - )+ - ) => { - paste! { - $( - decl_secret_bytes!( - [< Entropy $byte_count Bytes >], - $byte_count - ); - )+ - - $( - #[doc = $expr] - )* - #[derive(Zeroize, uniffi::Enum)] - pub enum $enum_name { - $( - [< EntropyOf $byte_count Bytes >]([< Entropy $byte_count Bytes >]), - )+ - } - - $( - impl From< [< Entropy $byte_count Bytes >] > for $enum_name { - fn from(value: [< Entropy $byte_count Bytes >]) -> Self { - Self::[< EntropyOf $byte_count Bytes >](value) - } - } - - impl From< [u8; $byte_count] > for $enum_name { - fn from(value: [u8; $byte_count]) -> Self { - Self::from([< Entropy $byte_count Bytes >]::new(value)) - } - } - - impl From<[< Entropy $byte_count Bytes >]> for NonEmptyMax32Bytes { - fn from(value: [< Entropy $byte_count Bytes >]) -> Self { - let bytes: &[u8] = value.secret_magic.0.as_ref(); - assert!(bytes.len() <= 32); - NonEmptyMax32Bytes::try_from(bytes).unwrap() - } - } - - impl TryFrom for [< Entropy $byte_count Bytes >] { - type Error = CommonError; - fn try_from(value: NonEmptyMax32Bytes) -> Result { - let b: &[u8; $byte_count] = value.as_ref().try_into().map_err(|_| CommonError::Unknown)?; - Ok(Self::from([< Entropy $byte_count Bytes >]::new(*b))) - } - } - )+ - - impl TryFrom for $enum_name { - type Error = CommonError; - fn try_from(value: NonEmptyMax32Bytes) -> Result<$enum_name> { - Err(CommonError::Unknown) - $( - .or([< Entropy $byte_count Bytes >]::try_from(value.clone()).map(Self::from)) - )+ - } - } - - impl $enum_name { - #[allow(clippy::wrong_self_convention)] // cannot be `(self)` since we impl drop. - fn into_bytes(&self) -> Vec { - match self { - $( - Self::[< EntropyOf $byte_count Bytes >](bytes) => Vec::from_iter(*bytes.secret_magic.0), - )+ - } - } - - #[allow(unused)] - pub(crate) fn is_zeroized(&self) -> bool { - match self { - $( - Self::[< EntropyOf $byte_count Bytes >](bytes) => bytes.is_zeroized(), - )+ - } - } - } - - impl From<$enum_name> for NonEmptyMax32Bytes { - fn from(value: $enum_name) -> NonEmptyMax32Bytes { - NonEmptyMax32Bytes::try_from(value.into_bytes()).expect("Never more than 32 bytes, and never empty.") - } - } - } - } -} - -entropy_with_byte_counts!( - /// BIP39 entropy, ranging from 16-32 bytes with discrete values being multiples of in between the range. - BIP39Entropy: 16, 20, 24, 28, 32, -); - -impl Mnemonic { - pub fn from_entropy_in( - entropy: BIP39Entropy, - language: BIP39Language, - ) -> Self { - let internal = bip39::Mnemonic::from_entropy_in( - language.into(), - NonEmptyMax32Bytes::from(entropy).as_ref(), - ) - .unwrap(); - Self::from_internal(internal) - } - - pub fn from_entropy(entropy: BIP39Entropy) -> Self { - Self::from_entropy_in(entropy, BIP39Language::English) - } - - pub fn generate_new() -> Self { - Self::from_entropy(BIP39Entropy::from(Entropy32Bytes::new( - generate_byte_array::<32>(), - ))) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = BIP39Entropy; - - #[test] - fn zeroize() { - let mut sut = SUT::from(Entropy16Bytes::new([0xff; 16])); - assert!(!sut.is_zeroized()); - sut.zeroize(); - assert!(sut.is_zeroized()); - } - - #[test] - fn mnemonic_from_entropy_of_16_bytes() { - let sut = SUT::from(Entropy16Bytes::new([0xff; 16])); - let mnemonic = Mnemonic::from_entropy(sut); - assert_eq!( - mnemonic.phrase(), - "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong" - ) - } - - #[test] - fn mnemonic_from_entropy_of_20_bytes() { - let sut = SUT::from(Entropy20Bytes::new([0xff; 20])); - let mnemonic = Mnemonic::from_entropy(sut); - assert_eq!( - mnemonic.phrase(), - "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrist" - ) - } - - #[test] - fn mnemonic_from_entropy_of_24_bytes() { - let sut = SUT::from(Entropy24Bytes::new([0xff; 24])); - let mnemonic = Mnemonic::from_entropy(sut); - assert_eq!( - mnemonic.phrase(), - "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when" - ) - } - - #[test] - fn mnemonic_from_entropy_of_28_bytes() { - let sut = SUT::from(Entropy28Bytes::new([0xff; 28])); - let mnemonic = Mnemonic::from_entropy(sut); - assert_eq!( - mnemonic.phrase(), - "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo veteran" - ); - } - - #[test] - fn mnemonic_from_entropy_of_32_bytes() { - let sut = SUT::from(Entropy32Bytes::new([0xff; 32])); - let mnemonic = Mnemonic::from_entropy(sut); - assert_eq!( - mnemonic.phrase(), - "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote" - ) - } -} diff --git a/src/hierarchical_deterministic/bip39/bip39_seed.rs b/src/hierarchical_deterministic/bip39/bip39_seed.rs deleted file mode 100644 index 679fff2be..000000000 --- a/src/hierarchical_deterministic/bip39/bip39_seed.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::{decl_secret_bytes, prelude::*}; - -decl_secret_bytes!( - /// A BIP39 seed for hierarchal deterministic wallets, as per the [BIP39 standard][doc]. - /// - /// We typically obtain this by calling [`to_seed` on `MnemonicWithPassphrase`][MnemonicWithPassphrase::to_seed]. - /// - /// [doc]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#user-content-From_mnemonic_to_seed - BIP39Seed, - 64 -); - -impl HDPath { - fn hardened_chain(&self) -> Vec { - self.components - .iter() - .map(|c| c.value) - .map(|v| IotaSlip10PathComponent::try_from(v).expect("Should work")) - .collect_vec() - } -} - -use crypto::{ - keys::slip10::{self as IotaSlip10, Hardened as IotaSlip10PathComponent}, - signatures::ed25519 as IotaSlip10Ed25519, - signatures::secp256k1_ecdsa as IotaSlip10Secp256k1, -}; - -impl BIP39Seed { - fn derive_slip10_private_key(&self, chain: I) -> IotaSlip10::Slip10 - where - K: IotaSlip10::IsSecretKey - + IotaSlip10::WithSegment<::Item>, - I: Iterator, - ::Item: IotaSlip10::Segment, - { - let iota_seed = IotaSlip10::Seed::from_bytes(&*self.secret_magic.0); - iota_seed.derive(chain) - // `IotaSlip10::Seed` implements `ZeroizeOnDrop` so should now be zeroized. - } - - fn derive_ed25519_private_key(&self, path: &HDPath) -> Ed25519PrivateKey { - let ck = self - .derive_slip10_private_key::( - path.hardened_chain().into_iter(), - ); - Ed25519PrivateKey::from_bytes(ck.secret_key().as_slice()) - .expect("Valid Ed25519PrivateKey bytes") - // `IotaSlip10Ed25519::SecretKey` implements `ZeroizeOnDrop` so should now be zeroized. - } - - pub(crate) fn derive_secp256k1_private_key( - &self, - path: &HDPath, - ) -> Secp256k1PrivateKey { - let ck = self - .derive_slip10_private_key::( - path.components.iter().cloned().map(|c| c.value), - ); - Secp256k1PrivateKey::from_bytes(&*ck.secret_key().to_bytes()) - .expect("Valid Secp256k1PrivateKey bytes") - // `IotaSlip10Ed25519::SecretKey` implements `ZeroizeOnDrop` so should now be zeroized. - } - - pub fn derive_private_key( - &self, - derivation: &D, - ) -> HierarchicalDeterministicPrivateKey - where - D: Derivation, - { - match derivation.curve() { - SLIP10Curve::Curve25519 => { - let key = self.derive_ed25519_private_key(derivation.hd_path()); - HierarchicalDeterministicPrivateKey::new( - key.into(), - derivation.derivation_path(), - ) - } - SLIP10Curve::Secp256k1 => { - let key = - self.derive_secp256k1_private_key(derivation.hd_path()); - HierarchicalDeterministicPrivateKey::new( - key.into(), - derivation.derivation_path(), - ) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = BIP39Seed; - - #[test] - fn zeroize() { - let mut sut: SUT = MnemonicWithPassphrase::sample().to_seed(); - assert!(!sut.is_zeroized()); - sut.zeroize(); - assert!(sut.is_zeroized()); - } - - #[test] - fn manual_uniffi_conversion() { - let bytes = Exactly64Bytes::sample(); - let builtin: BagOfBytes = bytes.clone().as_ref().into(); - let sut = new_b_i_p39_seed_from_bytes(builtin.clone()).unwrap(); - let rust_side = sut.secret_magic; - - let ffi_side = - ::from_custom( - rust_side, - ); - - assert_eq!(ffi_side.to_hex(), builtin.to_hex()); - - let from_ffi_side = - ::into_custom( - ffi_side, - ) - .unwrap(); - - assert_eq!( - new_b_i_p39_seed_from_bytes(builtin.clone()) - .unwrap() - .secret_magic - .0, - from_ffi_side.0 - ); - } -} diff --git a/src/hierarchical_deterministic/mod.rs b/src/hierarchical_deterministic/mod.rs deleted file mode 100644 index 38aa1aa1d..000000000 --- a/src/hierarchical_deterministic/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod bip32; -mod bip39; -mod bip44; -mod cap26; -mod derivation; - -pub use bip32::*; -pub use bip39::*; -pub use bip44::*; -pub use cap26::*; -pub use derivation::*; diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs deleted file mode 100644 index a94d0dcda..000000000 --- a/src/http_client/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod client; -mod network_antenna; - -pub use client::*; -pub use network_antenna::*; diff --git a/src/profile/logic/account/mod.rs b/src/profile/logic/account/mod.rs deleted file mode 100644 index ab1a767d0..000000000 --- a/src/profile/logic/account/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod accounts_visibility; - -pub use accounts_visibility::*; diff --git a/src/profile/logic/persona/mod.rs b/src/profile/logic/persona/mod.rs deleted file mode 100644 index 7cc782402..000000000 --- a/src/profile/logic/persona/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod persona_data_ids; -mod personas_visibility; -mod shared_persona_data_ids; - -pub use persona_data_ids::*; -pub use personas_visibility::*; -pub use shared_persona_data_ids::*; diff --git a/src/profile/mod.rs b/src/profile/mod.rs deleted file mode 100644 index e025d8d69..000000000 --- a/src/profile/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod encrypted; -mod logic; -mod profilesnapshot_version; -mod supporting_types; -mod v100; - -pub use encrypted::*; -pub use logic::*; -pub use profilesnapshot_version::*; -pub use supporting_types::*; -pub use v100::*; diff --git a/src/profile/supporting_types/decl_identified_vec_of_with_samples.rs b/src/profile/supporting_types/decl_identified_vec_of_with_samples.rs deleted file mode 100644 index 1bbae0a0e..000000000 --- a/src/profile/supporting_types/decl_identified_vec_of_with_samples.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::prelude::*; - -macro_rules! decl_identified_vec_of { - ( - $( - #[doc = $expr: expr] - )* - $collection_type: ident, - $element_type: ident - ) => { - paste! { - $( - #[doc = $expr] - )* - pub type $collection_type = IdentifiedVecOf<$element_type>; - - #[uniffi::export] - pub fn [< new_ $collection_type:snake _sample >]() -> $collection_type { - $collection_type::sample() - } - - #[uniffi::export] - pub fn [< new_ $collection_type:snake _sample_other >]() -> $collection_type { - $collection_type::sample_other() - } - - #[cfg(test)] - mod [< $collection_type:snake _tests >] { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = $collection_type; - - #[test] - fn test_ids() { - assert_eq!(SUT::sample().ids().into_iter().cloned().collect_vec(), SUT::sample().get_all().into_iter().map(|i| i.id()).collect_vec()); - } - } - - #[cfg(test)] - mod [< $collection_type:snake _uniffi_tests >] { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = $collection_type; - - #[test] - fn hash_of_samples() { - assert_eq!( - HashSet::::from_iter([ - [< new_ $collection_type:snake _sample >](), - [< new_ $collection_type:snake _sample_other >](), - // duplicates should get removed - [< new_ $collection_type:snake _sample >](), - [< new_ $collection_type:snake _sample_other >]() - ]) - .len(), - 2 - ); - } - - #[test] - fn manual_perform_uniffi_conversion_successful() { - let test = |sut: SUT| { - let ffi_side = >::lower(sut.clone()); - let from_ffi = - >::try_lift(ffi_side).unwrap(); - assert_eq!(from_ffi, sut); - }; - - test(SUT::sample()); - test(SUT::sample_other()); - } - } - } - }; - ( - $( - #[doc = $expr: expr] - )* - $element_type: ident - ) => { - paste! { - decl_identified_vec_of!( - $( - #[doc = $expr] - )* - [< $element_type s>], - $element_type - ); - } - }; -} - -pub(crate) use decl_identified_vec_of; diff --git a/src/profile/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs b/src/profile/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs deleted file mode 100644 index 3241e6dfb..000000000 --- a/src/profile/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::prelude::*; - -/// Properties describing a DeviceFactorSource to help user disambiguate between -/// it and another one. -#[derive( - Serialize, - Deserialize, - Debug, - Clone, - PartialEq, - Eq, - Hash, - derive_more::Display, - uniffi::Record, -)] -#[serde(rename_all = "camelCase")] -#[display("{name} {model}")] -pub struct DeviceFactorSourceHint { - /// "iPhone RED" - pub name: String, - - /// "iPhone SE 2nd gen" - pub model: String, - - /// The number of words in the mnemonic of a DeviceFactorSource, according to the BIP39 - /// standard, a multiple of 3, from 12 to 24 words. - pub mnemonic_word_count: BIP39WordCount, -} - -impl DeviceFactorSourceHint { - /// Instantiates a new DeviceFactorSourceHint from the specified name, model and word count. - pub fn new( - name: String, - model: String, - word_count: BIP39WordCount, - ) -> Self { - Self { - name, - model, - mnemonic_word_count: word_count, - } - } - - pub fn unknown_model_of_client( - word_count: BIP39WordCount, - wallet_client_model: WalletClientModel, - ) -> Self { - Self::new( - "Unknown Name".to_string(), - wallet_client_model.to_string(), - word_count, - ) - } - - pub fn iphone_unknown_model_with_word_count( - word_count: BIP39WordCount, - ) -> Self { - Self::unknown_model_of_client(word_count, WalletClientModel::Iphone) - } -} - -impl HasSampleValues for DeviceFactorSourceHint { - /// A sample used to facilitate unit tests. - fn sample() -> Self { - Self::sample_iphone_unknown() - } - - fn sample_other() -> Self { - Self::new( - "Android".to_string(), - "Samsung Galaxy S23 Ultra".to_string(), - BIP39WordCount::Twelve, - ) - } -} - -impl DeviceFactorSourceHint { - /// A sample used to facilitate unit tests. - pub fn sample_iphone_unknown() -> Self { - Self::iphone_unknown_model_with_word_count(BIP39WordCount::TwentyFour) - } -} - -#[cfg(test)] -mod tests { - use crate::prelude::*; - #[test] - fn equality() { - assert_eq!( - DeviceFactorSourceHint::sample(), - DeviceFactorSourceHint::sample() - ); - assert_eq!( - DeviceFactorSourceHint::sample_other(), - DeviceFactorSourceHint::sample_other() - ); - } - - #[test] - fn inequality() { - assert_ne!( - DeviceFactorSourceHint::sample(), - DeviceFactorSourceHint::sample_other() - ); - } - - #[test] - fn set_model() { - let mut sut = DeviceFactorSourceHint::sample(); - assert_eq!(sut.model, "iPhone".to_string()); - sut.model = "Android".to_string(); - assert_eq!(sut.model, "Android".to_string()); - } - - #[test] - fn set_name() { - let mut sut = DeviceFactorSourceHint::sample(); - sut.name = "Foo".to_string(); - assert_eq!(sut.name, "Foo".to_string()); - } - - #[test] - fn get_word_count() { - assert_eq!( - DeviceFactorSourceHint::sample().mnemonic_word_count, - BIP39WordCount::TwentyFour - ); - } - - #[test] - fn json() { - let model = DeviceFactorSourceHint::sample_iphone_unknown(); - assert_eq_after_json_roundtrip( - &model, - r#" - { - "name": "Unknown Name", - "model": "iPhone", - "mnemonicWordCount": 24 - } - "#, - ) - } -} diff --git a/src/profile/v100/header/device_info.rs b/src/profile/v100/header/device_info.rs deleted file mode 100644 index 016aead8a..000000000 --- a/src/profile/v100/header/device_info.rs +++ /dev/null @@ -1,255 +0,0 @@ -use crate::prelude::*; - -/// A short summary of a device the Profile is being used -/// on, typically an iPhone or an Android phone. -#[derive( - Serialize, - Deserialize, - Clone, - Debug, - PartialEq, - Eq, - Hash, - derive_more::Display, - uniffi::Record, -)] -#[display("{} | created: {} | #{}", description, self.date.date(), id.to_string())] -pub struct DeviceInfo { - /// A best effort stable and unique identifier of this - /// device. - /// - /// Apple has made it so that iOS devices cannot - /// query iOS for a unique identifier of the device, thus - /// the iOS team has made their own impl of a best effort - /// stable identifier. - pub id: Uuid, - - /// The date this description of the device was made, might - /// be equal to when the app was first ever launched on the - /// device. - pub date: Timestamp, - - /// A short description of the device, we devices should - /// read the device model and a given name from the device - /// if they are able to. - /// - /// E.g. "My Red Phone (iPhone SE 2nd Gen)" - pub description: String, -} - -impl DeviceInfo { - /// Instantiates a new `DeviceInfo` with `id`, `date` and `description`. - pub fn new( - id: Uuid, - date: Timestamp, - description: impl AsRef, - ) -> Self { - Self { - id, - date, - description: description.as_ref().to_owned(), - } - } - - /// Instantiates a new `DeviceInfo` with `description`, and generates a new `id` - /// and will use the current `date` for creation date. - pub fn with_description(description: impl AsRef) -> Self { - Self::new(id(), now(), description) - } - - /// Instantiates a new `DeviceInfo` with "iPhone" as description, and - /// generates a new `id` and will use the current `date` for creation date. - pub fn new_iphone() -> Self { - Self::with_description("iPhone") - } - - /// Instantiates a new `DeviceInfo` with "Unknown device" as description, and - /// generates a new `id` and will use the current `date` for creation date. - pub fn new_unknown_device() -> Self { - Self::with_description("Unknown device") - } -} - -impl Default for DeviceInfo { - fn default() -> Self { - Self::new_unknown_device() - } -} - -impl HasSampleValues for DeviceInfo { - fn sample() -> Self { - Self::new( - Uuid::from_str("66F07CA2-A9D9-49E5-8152-77ACA3D1DD74").unwrap(), - Timestamp::parse("2023-09-11T16:05:56Z").unwrap(), - "iPhone", - ) - } - - fn sample_other() -> Self { - Self::new( - Uuid::from_str("f07ca662-d9a9-9e45-1582-aca773d174dd").unwrap(), - Timestamp::parse("2023-12-24T17:13:56.123Z").unwrap(), - "Android", - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = DeviceInfo; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } - - #[test] - fn new_iphone() { - assert_eq!(SUT::new_iphone().description, "iPhone"); - } - - #[test] - fn with_description() { - assert_eq!(SUT::with_description("Nokia").description, "Nokia"); - } - - #[test] - fn new_has_description_unknown_device() { - assert_eq!(SUT::new_unknown_device().description, "Unknown device"); - } - - #[test] - fn display() { - let id_str = "12345678-bbbb-cccc-dddd-abcd12345678"; - let id = Uuid::from_str(id_str).unwrap(); - let sut = SUT::new( - id, - Timestamp::parse("2023-09-11T16:05:56Z").unwrap(), - "Foo", - ); - assert_eq!( - format!("{sut}"), - format!("Foo | created: 2023-09-11 | #{}", id_str) - ) - } - - #[test] - fn id_is_unique() { - let n = 1000; - let ids = (0..n) - .map(|_| SUT::new_iphone()) - .map(|d| d.id) - .collect::>(); - assert_eq!(ids.len(), n); - } - - #[test] - fn date_is_now() { - assert!(SUT::new_iphone().date.year() >= 2023); - } - - #[test] - fn can_parse_iso8601_json_without_milliseconds_precision() { - let str = r#" - { - "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", - "date": "2023-09-11T16:05:56Z", - "description": "iPhone" - } - "#; - let model = serde_json::from_str::(str).unwrap(); - assert_eq!(model.date.day(), 11); - let json = serde_json::to_string(&model).unwrap(); - assert!(json.contains("56.000Z")); - } - - #[test] - fn json_roundtrip() { - let model = SUT::sample(); - assert_eq_after_json_roundtrip( - &model, - // The JSON string literal below contains `.000` ISO8601 - // milliseconds which is not set on the sample - r#" - { - "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", - "date": "2023-09-11T16:05:56.000Z", - "description": "iPhone" - } - "#, - ); - assert_json_roundtrip(&model); - assert_ne_after_json_roundtrip( - &model, - r#" - { - "id": "00000000-0000-0000-0000-000000000000", - "date": "1970-01-01T12:34:56Z", - "description": "Nokia" - } - "#, - ); - } - - #[test] - fn invalid_json() { - assert_json_fails::( - r#" - { - "id": "invalid-uuid", - "date": "1970-01-01T12:34:56.000Z", - "description": "iPhone" - } - "#, - ); - - assert_json_fails::( - r#" - { - "id": "00000000-0000-0000-0000-000000000000", - "date": "invalid-date", - "description": "iPhone" - } - "#, - ); - - assert_json_fails::( - r#" - { - "missing_key": "id", - "date": "1970-01-01T12:34:56.000Z", - "description": "iPhone" - } - "#, - ); - - assert_json_fails::( - r#" - { - "id": "00000000-0000-0000-0000-000000000000", - "missing_key": "date", - "description": "iPhone" - } - "#, - ); - - assert_json_fails::( - r#" - { - "id": "00000000-0000-0000-0000-000000000000", - "date": "1970-01-01T12:34:56.000Z", - "missing_key": "description" - } - "#, - ); - } -} diff --git a/src/radix_connect/mobile/relay_service/mod.rs b/src/radix_connect/mobile/relay_service/mod.rs deleted file mode 100644 index f55d9461b..000000000 --- a/src/radix_connect/mobile/relay_service/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod models; -mod service; - -pub use service::*; diff --git a/src/radix_connect/mobile/relay_service/models/mod.rs b/src/radix_connect/mobile/relay_service/models/mod.rs deleted file mode 100644 index b8be6322b..000000000 --- a/src/radix_connect/mobile/relay_service/models/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod request; -mod response; - -pub use request::*; -pub use response::*; diff --git a/src/radix_connect/mod.rs b/src/radix_connect/mod.rs deleted file mode 100644 index 3affb0cec..000000000 --- a/src/radix_connect/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -mod interaction_id; -mod interaction_version; -mod p2p_links; -mod wallet_account; -mod wallet_interaction; -mod wallet_persona; - -#[allow(dead_code)] -mod mobile; - -pub use interaction_id::*; -pub use interaction_version::*; -pub use p2p_links::*; -pub use wallet_account::*; -pub use wallet_interaction::*; -pub use wallet_persona::*; diff --git a/src/sargon.udl b/src/sargon.udl deleted file mode 100644 index 16b2dc290..000000000 --- a/src/sargon.udl +++ /dev/null @@ -1,11 +0,0 @@ - -namespace sargon {}; - -// HERE BE DRAGONS -// Due to Kotlin equals being broken for ByteArray -// Which otherwise UniFFI converts `Vec` to, we MUST use -// our own "bag of bytes" which we convert to a Kotlin `List` -// which DOES have a working equals! -// HERE BE DRAGONS -[Custom] -typedef sequence BagOfBytes; \ No newline at end of file diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs deleted file mode 100644 index 599d3679f..000000000 --- a/src/wallet/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -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/secure_storage/always_fail_storage.rs b/src/wallet/secure_storage/always_fail_storage.rs deleted file mode 100644 index a8a0d5b1a..000000000 --- a/src/wallet/secure_storage/always_fail_storage.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![cfg(test)] - -use crate::prelude::*; - -#[derive(Debug)] -pub(crate) struct AlwaysFailStorage {} - -impl SecureStorage for AlwaysFailStorage { - fn load_data(&self, _key: SecureStorageKey) -> Result>> { - panic!("AlwaysFailStorage does not implement `load_data"); - } - - fn save_data(&self, _key: SecureStorageKey, _data: Vec) -> Result<()> { - Err(CommonError::Unknown) - } - - fn delete_data_for_key(&self, _key: SecureStorageKey) -> Result<()> { - panic!("AlwaysFailStorage does not implement `delete_data_for_key"); - } -} diff --git a/src/wallet/secure_storage/mod.rs b/src/wallet/secure_storage/mod.rs deleted file mode 100644 index ec2aaff90..000000000 --- a/src/wallet/secure_storage/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod secure_storage; -mod secure_storage_key; -mod wallet_client_storage; - -pub use secure_storage::*; -pub use secure_storage_key::*; -pub use wallet_client_storage::*; - -#[cfg(test)] -mod ephemeral_secure_storage; - -#[cfg(test)] -pub use ephemeral_secure_storage::*; - -#[cfg(test)] -mod always_fail_storage; - -#[cfg(test)] -pub use always_fail_storage::*; diff --git a/src/wallet/secure_storage/secure_storage.rs b/src/wallet/secure_storage/secure_storage.rs deleted file mode 100644 index 983cc3fc9..000000000 --- a/src/wallet/secure_storage/secure_storage.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::prelude::*; - -#[uniffi::export(with_foreign)] -pub trait SecureStorage: Send + Sync + std::fmt::Debug { - fn load_data(&self, key: SecureStorageKey) -> Result>>; - fn save_data(&self, key: SecureStorageKey, data: Vec) -> Result<()>; - fn delete_data_for_key(&self, key: SecureStorageKey) -> Result<()>; -} diff --git a/src/wallet/secure_storage/wallet_client_storage.rs b/src/wallet/secure_storage/wallet_client_storage.rs deleted file mode 100644 index 96b3dfbcc..000000000 --- a/src/wallet/secure_storage/wallet_client_storage.rs +++ /dev/null @@ -1,324 +0,0 @@ -use crate::prelude::*; - -/// An abstraction of an implementing WalletClients's secure storage, used by `Wallet` to -/// save and load models, most prominently `Profile` and `MnemonicWithPassphrase`. -/// -/// It uses the lower level CRUD trait `SecureStorage` which works on bytes (Vec), -/// by instead working with JSON. -/// -/// The typical usage is that `Wallet` uses this to build even higher level API's that work -/// with application level types such as `PrivateHierarchicalDeterministicFactorSource`, which -/// apart from `MnemonicWithPassphrase` read from SecureStorage using this `WalletClientStorage`, -/// also has to load the DeviceFactorSource from Profile, given a FactorSourceID only. -#[derive(Debug)] -pub struct WalletClientStorage { - /// Low level CRUD traits injected from implementing Wallet Client, that works on bytes. - interface: Arc, -} - -impl WalletClientStorage { - /// Creates a new WalletClientStorage using an implementation of - /// `SecureStorage`. - pub(crate) fn new(interface: Arc) -> Self { - Self { interface } - } -} - -//====== -// Save T -//====== -impl WalletClientStorage { - pub fn save(&self, key: SecureStorageKey, value: &T) -> Result<()> - where - T: serde::Serialize, - { - serde_json::to_vec(value) - .map_err(|_| CommonError::FailedToSerializeToJSON) - .and_then(|j| self.interface.save_data(key, j)) - } -} - -//====== -// Load T -//====== -impl WalletClientStorage { - /// Loads bytes from SecureStorage and deserializes them into `T`. - /// - /// Returns `Ok(None)` if no bytes were found, returns Err if failed - /// to load bytes or failed to deserialize the JSON into a `T`. - #[cfg(not(tarpaulin_include))] // false negative - pub fn load(&self, key: SecureStorageKey) -> Result> - where - T: for<'a> serde::Deserialize<'a>, - { - self.interface.load_data(key).and_then(|o| match o { - None => Ok(None), - Some(j) => serde_json::from_slice(j.as_slice()).map_err(|_| { - let type_name = std::any::type_name::().to_string(); - error!( - "Deserialize json to type: {}\nJSON (utf8):\n{:?}", - &type_name, - String::from_utf8(j.clone()) - ); - CommonError::FailedToDeserializeJSONToValue { - json_byte_count: j.len() as u64, - type_name, - } - }), - }) - } - - /// Loads bytes from SecureStorage and deserializes them into `T`. - /// - /// Returns Err if failed to load bytes or failed to deserialize the JSON into a `T`, - /// unlike `load` this method returns an error if `None` bytes were found. - pub fn load_or( - &self, - key: SecureStorageKey, - err: CommonError, - ) -> Result - where - T: for<'a> serde::Deserialize<'a>, - { - self.load(key).and_then(|o| o.ok_or(err)) - } - - /// Loads bytes from SecureStorage and deserializes them into `T`. - /// - /// Returns Err if failed to load bytes or failed to deserialize the JSON into a `T`, - /// unlike `load` this method returns `default` if `None` bytes were found. - pub fn load_unwrap_or(&self, key: SecureStorageKey, default: T) -> T - where - T: for<'a> serde::Deserialize<'a> + Clone, - { - self.load(key) - .map(|o| o.unwrap_or(default.clone())) - .unwrap_or(default) - } -} - -//====== -// Mnemonic CR(U)D -//====== -impl WalletClientStorage { - /// Saves a MnemonicWithPassphrase under a given `FactorSourceIDFromHash` - pub fn save_mnemonic_with_passphrase( - &self, - mnemonic_with_passphrase: &MnemonicWithPassphrase, - id: &FactorSourceIDFromHash, - ) -> Result<()> { - self.save( - SecureStorageKey::DeviceFactorSourceMnemonic { - factor_source_id: *id, - }, - mnemonic_with_passphrase, - ) - .map_err(|_| { - CommonError::UnableToSaveMnemonicToSecureStorage { bad_value: *id } - }) - } - - /// Loads a MnemonicWithPassphrase with a `FactorSourceIDFromHash` - pub fn load_mnemonic_with_passphrase( - &self, - id: &FactorSourceIDFromHash, - ) -> Result { - self.load_or( - SecureStorageKey::DeviceFactorSourceMnemonic { - factor_source_id: *id, - }, - CommonError::UnableToLoadMnemonicFromSecureStorage { - bad_value: *id, - }, - ) - } - - /// Deletes a MnemonicWithPassphrase with a `FactorSourceIDFromHash` - pub fn delete_mnemonic(&self, id: &FactorSourceIDFromHash) -> Result<()> { - self.interface.delete_data_for_key( - SecureStorageKey::DeviceFactorSourceMnemonic { - factor_source_id: *id, - }, - ) - } -} - -#[cfg(test)] -impl WalletClientStorage { - pub(crate) fn ephemeral( - ) -> (WalletClientStorage, Arc) { - let storage = EphemeralSecureStorage::new(); - (WalletClientStorage::new(storage.clone()), storage) - } - - pub(crate) fn always_fail() -> Self { - WalletClientStorage::new(Arc::new(AlwaysFailStorage {})) - } -} - -#[cfg(test)] -mod tests { - use ::hex::FromHex; - - use crate::{prelude::*, wallet::secure_storage::ephemeral_secure_storage}; - use std::{fmt::Write, sync::RwLock}; - - fn make_sut() -> WalletClientStorage { - WalletClientStorage::ephemeral().0 - } - - #[test] - fn load_ok_when_none() { - let sut = make_sut(); - assert_eq!( - sut.load::(SecureStorageKey::ActiveProfileID), - Ok(None) - ); - } - - #[test] - fn load_fail_to_deserialize_json() { - let sut = make_sut(); - - assert!(sut - .save( - SecureStorageKey::ActiveProfileID, - &0u8, // obviously a u8 is not a Profile - ) - .is_ok()); - assert_eq!( - sut.load::(SecureStorageKey::ActiveProfileID), - Err(CommonError::FailedToDeserializeJSONToValue { - json_byte_count: 1, - type_name: "sargon::profile::v100::profile::Profile" - .to_string() - }) - ); - } - - #[test] - fn load_successful() { - let sut = make_sut(); - - assert!(sut - .save(SecureStorageKey::ActiveProfileID, &Profile::sample()) - .is_ok()); - assert_eq!( - sut.load::(SecureStorageKey::ActiveProfileID), - Ok(Some(Profile::sample())) - ); - } - - #[test] - fn load_unwrap_or_some_default_not_used() { - let sut = make_sut(); - - assert!(sut - .save(SecureStorageKey::ActiveProfileID, &Profile::sample()) - .is_ok()); - assert_eq!( - sut.load_unwrap_or::( - SecureStorageKey::ActiveProfileID, - Profile::sample_other() - ), - Profile::sample() - ); - } - - #[test] - fn load_unwrap_or_none_default_is_used() { - let sut = make_sut(); - - assert_eq!( - sut.load_unwrap_or::( - SecureStorageKey::ActiveProfileID, - Profile::sample_other() - ), - Profile::sample_other() - ); - } - - #[test] - fn save_mnemonic_with_passphrase() { - let private = - PrivateHierarchicalDeterministicFactorSource::sample_other(); - let factor_source_id = private.factor_source.id; - let (sut, storage) = WalletClientStorage::ephemeral(); - let key = - SecureStorageKey::DeviceFactorSourceMnemonic { factor_source_id }; - assert_eq!(storage.load_data(key.clone()), Ok(None)); // not yet saved - assert!(sut - .save_mnemonic_with_passphrase( - &private.mnemonic_with_passphrase, - &factor_source_id.clone() - ) - .is_ok()); - - // Assert indeed was saved. - assert!(storage - .load_data(key) - .map(|b| String::from_utf8(b.unwrap()).unwrap()) - .unwrap() - .contains("zoo")); - } - - #[test] - fn save_mnemonic_with_passphrase_failure() { - let sut = WalletClientStorage::always_fail(); - let id = FactorSourceIDFromHash::sample(); - assert_eq!( - sut.save_mnemonic_with_passphrase( - &MnemonicWithPassphrase::sample(), - &id - ), - Err(CommonError::UnableToSaveMnemonicToSecureStorage { - bad_value: id - }) - ); - } - - #[test] - fn delete_mnemonic() { - // ARRANGE - let private = - PrivateHierarchicalDeterministicFactorSource::sample_other(); - let factor_source_id = private.factor_source.id; - let (sut, storage) = WalletClientStorage::ephemeral(); - let key = - SecureStorageKey::DeviceFactorSourceMnemonic { factor_source_id }; - assert!(storage.save_data(key.clone(), vec![0xde, 0xad]).is_ok()); - assert_eq!(storage.load_data(key.clone()), Ok(Some(vec![0xde, 0xad]))); // assert save worked - - // ACT - assert!(sut.delete_mnemonic(&factor_source_id).is_ok()); - - // ASSERT - assert_eq!(storage.load_data(key), Ok(None)); - } - - #[test] - fn save_fail_to_serialize() { - use serde::Serialize; - struct AlwaysFailSerialize {} - impl Serialize for AlwaysFailSerialize { - fn serialize( - &self, - _serializer: S, - ) -> core::result::Result - where - S: Serializer, - { - Err(serde::ser::Error::custom(CommonError::Unknown)) - } - } - - let (sut, _) = WalletClientStorage::ephemeral(); - assert_eq!( - sut.save( - SecureStorageKey::ActiveProfileID, - &AlwaysFailSerialize {} - ), - Err(CommonError::FailedToSerializeToJSON) - ); - } -} diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs deleted file mode 100644 index 7c3135230..000000000 --- a/src/wallet/wallet.rs +++ /dev/null @@ -1,374 +0,0 @@ -use crate::prelude::*; -use std::sync::{Once, RwLock, RwLockReadGuard, RwLockWriteGuard}; - -pub type HeadersList = IdentifiedVecOf

; - -#[derive(Debug, uniffi::Object)] -pub struct Wallet { - // This is pub(crate) for testing purposes only, i.e. causing the RwLock to be poisoned. - pub(crate) profile: RwLock, - pub(crate) wallet_client_storage: WalletClientStorage, -} - -impl Wallet { - /// Initializes logging - fn init_logging() { - static ONCE: Once = Once::new(); - ONCE.call_once(|| { - pretty_env_logger::formatted_builder() - .filter_level(log::LevelFilter::Trace) - .try_init() - .expect("Should be able to setup a logger."); - }); - } - - fn with_imported_profile( - profile: Profile, - secure_storage: Arc, - ) -> Self { - // Init WalletClient's storage - let wallet_client_storage = WalletClientStorage::new(secure_storage); - - // Init wallet - let wallet = Self { - profile: RwLock::new(profile.clone()), - wallet_client_storage, - }; - - // Save new profile (also sets activeProfileID) - wallet.save_new_profile_or_panic(&profile); - - wallet - } - - fn new_load_profile_with_id( - profile_id: ProfileID, - wallet_client_storage: WalletClientStorage, - ) -> Result { - // Form storage key - let profile_key = SecureStorageKey::ProfileSnapshot { profile_id }; - - // Load Profile from storage with key - let profile: Profile = wallet_client_storage.load_or( - profile_key, - CommonError::ProfileSnapshotNotFound { - bad_value: profile_id, - }, - )?; - - // Create wallet - let wallet = Self { - profile: RwLock::new(profile), - wallet_client_storage, - }; - - // Set active profile ID - wallet.save_active_profile_id_or_panic(&profile_id); - - Ok(wallet) - } -} - -//======== -// CONSTRUCTOR -//======== -#[uniffi::export] -impl Wallet { - /// Creates a new Mnemonic from `entropy` (without BIP39 passphrase) and creates a new Profile, - /// saving both the Mnemonic and Profile into secure storage and returns a new Wallet. - #[uniffi::constructor] - pub fn by_creating_new_profile_and_secrets_with_entropy( - entropy: NonEmptyMax32Bytes, // yes would be more correct to pass `BIP39Entropy` here, but I wanna avoid UniFFI exporting it. - wallet_client_model: WalletClientModel, - wallet_client_name: String, - secure_storage: Arc, - ) -> Result { - Wallet::init_logging(); - - log::info!("Instantiating Wallet by creating a new Profile from entropy (provided), for client: {}", wallet_client_model); - let entropy = BIP39Entropy::try_from(entropy)?; - - let private_hd_factor_source = - PrivateHierarchicalDeterministicFactorSource::new_babylon_with_entropy( - true, - entropy, - BIP39Passphrase::default(), - wallet_client_model, - ); - - let profile = Profile::new( - private_hd_factor_source.factor_source.clone(), - wallet_client_name.as_str(), - ); - - let wallet = Self::with_imported_profile(profile, secure_storage); - - wallet.wallet_client_storage.save( - SecureStorageKey::DeviceFactorSourceMnemonic { - factor_source_id: private_hd_factor_source.factor_source.id, - }, - &private_hd_factor_source.mnemonic_with_passphrase, - )?; - - Ok(wallet) - } - - /// Creates wallet by *importing* a Profile. - #[uniffi::constructor] - pub fn by_importing_profile( - profile: Profile, - secure_storage: Arc, - ) -> Self { - Wallet::init_logging(); - - log::info!( - "Instantiating Wallet by importing a Profile with ID: {}", - profile.id() - ); - - Self::with_imported_profile(profile, secure_storage) - } - - #[uniffi::constructor] - pub fn by_loading_profile( - secure_storage: Arc, - ) -> Result { - Wallet::init_logging(); - - log::info!( - "Instantiating Wallet by loading the active Profile from storage" - ); - - // Init WalletClient's storage - let wallet_client_storage = WalletClientStorage::new(secure_storage); - - // Load active profile ID - let active_profile_id: ProfileID = wallet_client_storage.load_or( - SecureStorageKey::ActiveProfileID, - CommonError::NoActiveProfileIDSet, - )?; - - Self::new_load_profile_with_id(active_profile_id, wallet_client_storage) - } - - #[uniffi::constructor] - pub fn by_loading_profile_with_id( - profile_id: ProfileID, - secure_storage: Arc, - ) -> Result { - Wallet::init_logging(); - - log::info!( - "Instantiating Wallet by loading the Profile with ID {} from storage", - profile_id - ); - - Self::new_load_profile_with_id( - profile_id, - WalletClientStorage::new(secure_storage), - ) - } -} - -#[cfg(test)] -impl Wallet { - pub(crate) fn ephemeral( - profile: Profile, - ) -> (Self, Arc) { - let storage = EphemeralSecureStorage::new(); - ( - Self::by_importing_profile(profile, storage.clone()), - storage, - ) - } -} -#[cfg(test)] -impl HasSampleValues for Wallet { - fn sample() -> Self { - Self::ephemeral(Profile::sample()).0 - } - - fn sample_other() -> Self { - Self::ephemeral(Profile::sample_other()).0 - } -} - -//======== -// GET -//======== -#[uniffi::export] -impl Wallet { - /// Takes a snapshot of the profile and serialize it as a String of JSON. - pub fn json_snapshot(&self) -> String { - serde_json::to_string(&self.profile()) - .expect("Should always be able to JSON serialize a Profile.") - } - - /// Clone the profile and return it. - pub fn profile(&self) -> Profile { - self.access_profile_with(|p| p.clone()) - } -} - -impl Wallet { - pub(crate) fn access_profile_with(&self, access: F) -> T - where - F: Fn(RwLockReadGuard<'_, Profile>) -> T, - { - self.profile - .try_read() - .map(access) - .expect("Implementing Wallet clients should not read and write Profile from Wallet from multiple threads.") - } - - pub(crate) fn update_profile_with(&self, mutate: F) -> R - where - F: Fn(RwLockWriteGuard<'_, Profile>) -> R, - { - let value = self.profile - .try_write() - .map(mutate) - .expect("Implementing Wallet clients should not read and write Profile from Wallet from multiple threads."); - - self.save_existing_profile() - .expect("Failed to save Profile to secure storage."); - - value - } - - #[cfg(not(tarpaulin_include))] // false negative - pub(crate) fn try_update_profile_with(&self, mutate: F) -> Result - where - F: Fn(RwLockWriteGuard<'_, Profile>) -> Result, - { - let res = self - .profile - .try_write() - .map_err(|_| CommonError::UnableToAcquireWriteLockForProfile) - .and_then(mutate)?; - - self.save_existing_profile()?; - - Ok(res) - } -} - -#[cfg(test)] -mod tests { - - use crate::prelude::*; - #[test] - fn read_header() { - let wallet = Wallet::sample(); - wallet.access_profile_with(|p| { - assert_eq!(p.header, Profile::sample().header) - }) - } - - #[test] - fn take_snapshot() { - let wallet = Wallet::sample(); - assert_eq!(wallet.profile(), Profile::sample()) - } -} - -#[cfg(test)] -mod uniffi_tests { - use crate::prelude::*; - - #[test] - fn by_loading_profile_with_id() { - let profile = Profile::sample(); - let secure_storage = EphemeralSecureStorage::new(); - let data = serde_json::to_vec(&profile).unwrap(); - assert!(secure_storage - .save_data( - SecureStorageKey::ProfileSnapshot { - profile_id: profile.id(), - }, - data, - ) - .is_ok()); - assert_eq!( - secure_storage.load_data(SecureStorageKey::ActiveProfileID), - Ok(None) - ); // we dont have any ActiveID yet. - let wallet = Wallet::by_loading_profile_with_id( - profile.id(), - secure_storage.clone(), - ) - .unwrap(); - assert_eq!(wallet.profile(), profile); - - // Assert an ActiveProfileID has been saved. - assert_eq!( - secure_storage.load_data(SecureStorageKey::ActiveProfileID), - Ok(Some(serde_json::to_vec(&profile.id()).unwrap())) - ); - } - - #[test] - fn by_loading_profile() { - let profile = Profile::sample(); - let secure_storage = EphemeralSecureStorage::new(); - let active_profile_id_data = serde_json::to_vec(&profile.id()).unwrap(); - assert!(secure_storage - .save_data( - SecureStorageKey::ActiveProfileID, - active_profile_id_data - ) - .is_ok()); - let data = serde_json::to_vec(&profile).unwrap(); - assert!(secure_storage - .save_data( - SecureStorageKey::ProfileSnapshot { - profile_id: profile.id(), - }, - data, - ) - .is_ok()); - let wallet = Wallet::by_loading_profile(secure_storage).unwrap(); - assert_eq!(wallet.profile(), profile); - } - - #[test] - fn snapshot_json() { - let profile = Profile::sample(); - let secure_storage = EphemeralSecureStorage::new(); - let wallet = - Wallet::by_importing_profile(profile.clone(), secure_storage); - let expected_json = serde_json::to_string(&profile).unwrap(); - assert_eq!(wallet.json_snapshot(), expected_json); - } - - #[test] - fn by_creating_new_profile_and_secrets_with_entropy() { - let secure_storage = EphemeralSecureStorage::new(); - let wallet = Wallet::by_creating_new_profile_and_secrets_with_entropy( - Entropy32Bytes::new([0xff; 32]).into(), - WalletClientModel::Unknown, - "Test".to_string(), - secure_storage.clone(), - ) - .unwrap(); - let mnemonic_json = secure_storage - .load_data(SecureStorageKey::DeviceFactorSourceMnemonic { - factor_source_id: wallet.profile().bdfs().id, - }) - .unwrap() - .unwrap(); - let mwp = - serde_json::from_slice::(&mnemonic_json) - .unwrap(); - assert_eq!(mwp.mnemonic.phrase(), "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote"); - - let active_id_data = secure_storage - .load_data(SecureStorageKey::ActiveProfileID) - .unwrap() - .unwrap(); - - let active_id = - serde_json::from_slice::(&active_id_data).unwrap(); - assert_eq!(active_id, wallet.profile().id()); - } -} diff --git a/src/wallet/wallet_accounts.rs b/src/wallet/wallet_accounts.rs deleted file mode 100644 index c4a77abad..000000000 --- a/src/wallet/wallet_accounts.rs +++ /dev/null @@ -1,559 +0,0 @@ -use crate::prelude::*; - -impl Wallet { - /// Adds a device factor source to Profile and SecureStorage, this method will only - /// return `Ok` if both the mnemonic was successfully saved to SecureStorage and the - /// DeviceFactorSource present in Profile and Profile also successfully updated in - /// SecureStorage. - /// - /// Returns `Err` if it is already present in Profile. It is Wallet Client - /// dependent if it throws if already present in SecureStorage. - /// - /// If saving of `MnemonicWithPassphrase` to SecureStorage succeeds, but adding - /// `DeviceFactorSource` to Profile/saving of Profile to SecureStorage fails, then - /// this method will try to remove the newly saved `MnemonicWithPassphrase` from - /// `SecureStorage`. - /// - /// Takes ownership of `PrivateHierarchicalDeterministicFactorSource` - pub fn add_private_device_factor_source( - &self, - private_device_factor_source: PrivateHierarchicalDeterministicFactorSource, - ) -> Result<()> { - let id = private_device_factor_source.factor_source.id; - - info!( - "Save Private DeviceFactorSource to SecureStorage, factor source id: {}", - &id - ); - - self.wallet_client_storage.save_mnemonic_with_passphrase( - &private_device_factor_source.mnemonic_with_passphrase, - &id, - )?; - - self.add_factor_source(private_device_factor_source.factor_source.into()) - .map_err(|e| { - error!( - "Failed to add Private DeviceFactorSource to SecureStorage, factor source id: {}", - id - ); - _ = self.wallet_client_storage.delete_mnemonic(&id); - e - }) - } - - /// Adds `factor_source` to Profile and takes a snapshot of Profile and - /// updates it in SecureStorage. - /// - /// Returns `Err` if `factor_source` is already present in factor source, - /// or if saving to SecureStorage fails. - /// - /// If only saving to SecureStorage fails, the Profile still remains - /// edited. - pub fn add_factor_source(&self, factor_source: FactorSource) -> Result<()> { - self.try_update_profile_with(|mut p| { - trace!( - "About to add FactorSource: {}, to list of factor sources: {}", - &factor_source, - &p.factor_sources - ); - if p.factor_sources.append(factor_source.to_owned()).0 { - debug!("Added FactorSource: {}", &factor_source); - Ok(()) - } else { - error!( - "FactorSource not added, already present: {}", - &factor_source - ); - Err(CommonError::Unknown) - } - }) - .map_err(|_| CommonError::UnableToSaveFactorSourceToProfile { - bad_value: factor_source.factor_source_id(), - }) - } -} - -//======== -// SET - Account -//======== -#[uniffi::export] -impl Wallet { - /// Creates a new non securified account **WITHOUT** add it to Profile, using the *main* "Babylon" - /// `DeviceFactorSource` and the "next" index for this FactorSource as derivation path. - /// - /// If you want to add it to Profile, call `wallet.add_account(account)` - pub fn create_new_account( - &self, - network_id: NetworkID, - name: DisplayName, - ) -> Result { - let profile = &self.profile(); - let bdfs = profile.bdfs(); - - let index = profile - .next_derivation_index_for_entity(EntityKind::Account, network_id); - - let number_of_accounts_on_network = profile - .networks - .get_id(&network_id) - .map(|n| n.accounts.len()) - .unwrap_or(0); - - let appearance_id = AppearanceID::from_number_of_accounts_on_network( - number_of_accounts_on_network, - ); - - let factor_instance = - self.load_private_device_factor_source(&bdfs).map(|p| { - p.derive_entity_creation_factor_instance(network_id, index) - })?; - - let account = Account::new(factor_instance, name, appearance_id); - - Ok(account) - } - - /// Returns `Ok(())` if the `account` was new and successfully added. If saving failed or if the account was already present in Profile, an - /// error is returned. - pub fn add_account(&self, account: Account) -> Result<()> { - // TODO: clean this up, BAD code. messy, mostly because of (my) bad IdentifiedVec API. - let network_id = account.network_id; - let err_exists = CommonError::AccountAlreadyPresent { - bad_value: account.id(), - }; - self.try_update_profile_with(|mut p| { - let networks = &mut p.networks; - if networks.contains_id(&network_id) { - networks.try_try_update_with(&network_id, |network| { - if network.accounts.append(account.clone()).0 { - Ok(()) - } else { - Err(err_exists.clone()) - } - })?; - Ok(()) - } else { - let network = ProfileNetwork::new( - network_id, - Accounts::from_iter([account.to_owned()]), - Personas::default(), - AuthorizedDapps::default(), - ); - networks.append(network); - Ok(()) - } - }) - } - - /// Create a new Account and adds it to the active Profile. - pub fn create_and_save_new_account( - &self, - network_id: NetworkID, - name: DisplayName, - ) -> Result { - let account = self.create_new_account(network_id, name)?; - self.add_account(account.clone())?; - Ok(account) - } - - /// Updates `account` as a whole, if it exists, else an error is thrown. - pub fn update_account(&self, to: Account) -> Result { - self.update_profile_with(|mut p| { - p.update_account(&to.address, |a| *a = to.to_owned()) - }) - .ok_or(CommonError::UnknownAccount) - } - - /// Updates the display name of account with the provided address, throws an error if the account is unknown to the wallet. - pub fn change_name_of_account( - &self, - address: AccountAddress, - to: DisplayName, - ) -> Result { - self.update_profile_with(|mut p| { - p.update_account(&address, |a| a.display_name = to.to_owned()) - }) - .ok_or(CommonError::UnknownAccount) - } -} - -#[cfg(test)] -mod tests { - - use std::{ - borrow::{Borrow, BorrowMut}, - ops::Deref, - sync::atomic::AtomicBool, - }; - - use crate::prelude::*; - pub use pretty_assertions::{assert_eq, assert_ne}; - use std::sync::RwLock; - - #[test] - fn change_display_name_of_accounts() { - let profile = Profile::sample(); - let (wallet, _) = Wallet::ephemeral(profile.clone()); - let account = - wallet.access_profile_with(|p| p.networks[0].accounts[0].clone()); - assert_eq!(account.display_name.value, "Alice"); - assert!(wallet - .change_name_of_account( - account.address, - DisplayName::new("Stella").unwrap() - ) - .is_ok()); - wallet.access_profile_with(|p| { - assert_eq!(p.networks[0].accounts[0].display_name.value, "Stella") - }); - - assert_eq!( - wallet.change_name_of_account( - AccountAddress::sample_other(), - DisplayName::new("not used").unwrap() - ), - Err(CommonError::UnknownAccount) - ); - } - - #[test] - fn update_account() { - let profile = Profile::sample(); - let (wallet, _) = Wallet::ephemeral(profile.clone()); - let mut account = - wallet.access_profile_with(|p| p.networks[0].accounts[0].clone()); - assert_eq!(account.display_name.value, "Alice"); - account.display_name = DisplayName::new("Stella").unwrap(); - account.appearance_id = AppearanceID::new(7).unwrap(); - - // Assert that `Account` returned by method `update_account` is the updated one. - assert_eq!( - wallet.update_account(account).unwrap().display_name.value, - "Stella" - ); - - // Assert account has been updated in `wallet.profile` - wallet.access_profile_with(|p| { - let account = &p.networks[0].accounts[0]; - assert_eq!(account.display_name.value, "Stella"); - assert_eq!(account.appearance_id.value, 7); - }); - } - - #[test] - fn load_private_device_factor_source() { - let private = PrivateHierarchicalDeterministicFactorSource::sample(); - let dfs = private.factor_source; - let profile = Profile::sample(); - 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, - }; - storage.save_data(key.clone(), data.clone()).unwrap(); - assert_eq!( - wallet - .load_private_device_factor_source(&dfs) - .unwrap() - .mnemonic_with_passphrase, - MnemonicWithPassphrase::sample() - ); - } - - #[test] - pub fn add_private_device_factor_source_successful() { - let profile = Profile::sample(); - let new = - PrivateHierarchicalDeterministicFactorSource::generate_new_babylon( - true, - WalletClientModel::Unknown, - ); - let (wallet, storage) = Wallet::ephemeral(profile.clone()); - assert_eq!( - profile - .factor_sources - .contains_id(&new.clone().factor_source.factor_source_id()), - false - ); - assert!(wallet.add_private_device_factor_source(new.clone()).is_ok()); - assert!(storage.storage.read().unwrap().contains_key( - &SecureStorageKey::DeviceFactorSourceMnemonic { - factor_source_id: new.clone().factor_source.id, - }, - )); - assert_eq!( - wallet - .profile() - .factor_sources - .contains_id(&new.clone().factor_source.factor_source_id()), - true - ); - } - - #[test] - pub fn add_private_device_factor_source_ok_storage_when_save_to_profile_fails_then_deleted_from_storage( - ) { - let profile = Profile::sample(); - let new = - PrivateHierarchicalDeterministicFactorSource::generate_new_babylon( - true, - WalletClientModel::Unknown, - ); - - assert_eq!( - profile - .factor_sources - .contains_id(&new.clone().factor_source.factor_source_id()), - false - ); - let delete_data_was_called = - Arc::new(RwLock::new(Option::::None)); - #[derive(Debug)] - struct TestStorage { - delete_data_was_called: Arc>>, - } - impl SecureStorage for TestStorage { - fn load_data( - &self, - _key: SecureStorageKey, - ) -> Result>> { - unreachable!() - } - - fn save_data( - &self, - _key: SecureStorageKey, - _data: Vec, - ) -> Result<()> { - Ok(()) // mnemonic gets saved - } - - fn delete_data_for_key(&self, key: SecureStorageKey) -> Result<()> { - let mut delete_data_was_called = - self.delete_data_was_called.write().unwrap(); - *delete_data_was_called = Some(key); - Ok(()) - } - } - let storage = Arc::new(TestStorage { - delete_data_was_called: delete_data_was_called.clone(), - }); - let wallet = Wallet::by_importing_profile(profile, storage.clone()); - - // Acquire write lock, in order to make `wallet.add_private_device_factor_source` fail (because cant have multiple writers). - let lock = wallet.profile.write().unwrap(); - - assert_eq!( - wallet.add_private_device_factor_source(new.clone()), - Err(CommonError::UnableToSaveFactorSourceToProfile { - bad_value: new.factor_source.factor_source_id() - }) - ); - drop(lock); - - assert_eq!( - wallet - .profile() - .factor_sources - .contains_id(&new.clone().factor_source.factor_source_id()), - false // should not have been saved. - ); - assert_eq!( - delete_data_was_called.read().unwrap().clone().unwrap(), - SecureStorageKey::DeviceFactorSourceMnemonic { - factor_source_id: new.clone().factor_source.id - } - ); - } - - #[test] - fn add_factor_source_fails_when_already_exists() { - let profile = Profile::sample(); - let other = PrivateHierarchicalDeterministicFactorSource::sample(); - let (wallet, _) = Wallet::ephemeral(profile.clone()); - assert_eq!( - wallet.add_factor_source(other.factor_source.clone().into()), - Err(CommonError::UnableToSaveFactorSourceToProfile { - bad_value: other.factor_source.factor_source_id() - }) - ) - } - - #[test] - fn load_private_device_factor_source_by_id() { - let profile = Profile::sample(); - let private = PrivateHierarchicalDeterministicFactorSource::sample(); - 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: private.clone().factor_source.id, - }; - assert!(storage.save_data(key.clone(), data).is_ok()); - - let loaded = wallet - .load_private_device_factor_source_by_id( - &private.factor_source.id.clone(), - ) - .unwrap(); - assert_eq!(loaded, private); - } - - #[test] - #[ignore] - fn generate_huge_profile_with_super_many_accounts() { - let private = PrivateHierarchicalDeterministicFactorSource::sample(); - - let (wallet, storage) = Wallet::ephemeral(Profile::new( - private.clone().factor_source, - "Test", - )); - - let data = - serde_json::to_vec(&private.mnemonic_with_passphrase).unwrap(); - let key = SecureStorageKey::DeviceFactorSourceMnemonic { - factor_source_id: private.clone().factor_source.id, - }; - assert!(storage.save_data(key.clone(), data).is_ok()); - - let network_id = NetworkID::Mainnet; - - let n = 100; - (0..n).for_each(|index| { - let account_name = - DisplayName::new(format!("Account {index}")).unwrap(); - let _ = wallet - .create_and_save_new_account(network_id, account_name.clone()) - .unwrap(); - }); - - let profile = wallet.profile(); - assert_eq!(profile.networks.first().unwrap().accounts.len(), n); - let profile_json = profile.to_json_bytes(); - - fs::write( - concat!(env!("FIXTURES_VECTOR"), "big_profile_100_accounts.json"), - profile_json, - ) - .expect("Unable to write file"); - } - - // Profile `init_profile`'s BDFS MUST eq `PrivateHierarchicalDeterministicFactorSource::sample()` - fn test_new_account( - init_profile: Profile, - also_save: bool, - assert_before: F, - assert_after: G, - ) where - F: Fn(Profile), - G: Fn(Account, Profile), - { - let private = PrivateHierarchicalDeterministicFactorSource::sample(); - assert_eq!( - init_profile.bdfs().factor_source_id(), - private.clone().factor_source.factor_source_id() - ); - - let (wallet, storage) = Wallet::ephemeral(init_profile); - assert_before(wallet.profile()); - - let data = - serde_json::to_vec(&private.mnemonic_with_passphrase).unwrap(); - let key = SecureStorageKey::DeviceFactorSourceMnemonic { - factor_source_id: private.clone().factor_source.id, - }; - assert!(storage.save_data(key.clone(), data).is_ok()); - - let account_name = DisplayName::new("Test").unwrap(); - let network_id = NetworkID::Mainnet; - let account = if also_save { - wallet.create_and_save_new_account(network_id, account_name.clone()) - } else { - wallet.create_new_account(network_id, account_name.clone()) - } - .unwrap(); - - assert_eq!(account.display_name, account_name); - assert_eq!(account.network_id, network_id); - - assert_after(account, wallet.profile()); - } - - fn test_create_new_account_first_success(also_save: bool, assert_last: F) - where - F: Fn(Account, Profile), - { - test_new_account( - Profile::new( - PrivateHierarchicalDeterministicFactorSource::sample() - .factor_source, - "Test", - ), - also_save, - |p| { - assert_eq!(p.networks.len(), 0); // no accounts yet, no networks even - }, - |a, q| { - assert_eq!( - a.address.address(), - "account_rdx12yy8n09a0w907vrjyj4hws2yptrm3rdjv84l9sr24e3w7pk7nuxst8" - ); - assert_eq!(a.appearance_id, AppearanceID::new(0).unwrap()); // using `0` since first. - - assert_last(a, q); - }, - ); - } - - #[test] - fn create_new_account_first_success() { - test_create_new_account_first_success(false, |_, q| { - assert_eq!(q.networks.len(), 0); - }); - - test_create_new_account_first_success(true, |a, q| { - assert_eq!(q.networks.len(), 1); - assert_eq!(q.networks[0].accounts[0], a); - }) - } - - fn test_create_new_account_not_first_success( - also_save: bool, - assert_last: F, - ) where - F: Fn(Account, Profile), - { - test_new_account( - Profile::sample(), - also_save, - |p| { - assert_eq!(p.networks[0].accounts.len(), 2); - }, - |a, q| { - assert_eq!( - a.address.address(), - "account_rdx12xvg2sssh0rpca6e8xyqv5vf4nqu928083yzf0fdrnvjdz2pvc000x" // pretty cool address! Random! - ); - assert_eq!(a.appearance_id, AppearanceID::new(2).unwrap()); - - assert_last(a, q); - }, - ); - } - - #[test] - fn create_new_account_not_first_success() { - test_create_new_account_not_first_success(false, |_, q| { - // Account SHOULD NOT yet have been saved into Profile, so number of accounts should still be 2 - assert_eq!(q.networks[0].accounts.len(), 2); - }); - - test_create_new_account_not_first_success(true, |a, q| { - assert_eq!(q.networks[0].accounts.len(), 3); - assert_eq!(q.networks[0].accounts[2], a); - }) - } -} diff --git a/src/wallet/wallet_profile_io.rs b/src/wallet/wallet_profile_io.rs deleted file mode 100644 index 0b9a188f7..000000000 --- a/src/wallet/wallet_profile_io.rs +++ /dev/null @@ -1,202 +0,0 @@ -use crate::prelude::*; - -//======== -// Wallet + SecureStorage -//======== -impl Wallet { - pub(crate) fn save_profile(&self, profile: &Profile) -> Result<()> { - self.wallet_client_storage.save( - SecureStorageKey::ProfileSnapshot { - profile_id: profile.header.id, - }, - profile, - ) - } - pub(crate) fn save_active_profile_id( - &self, - profile_id: &ProfileID, - ) -> Result<()> { - self.wallet_client_storage - .save(SecureStorageKey::ActiveProfileID, profile_id) - } - pub(crate) fn save_active_profile_id_or_panic( - &self, - profile_id: &ProfileID, - ) { - match self.save_active_profile_id(profile_id) { - Ok(_) => log::info!( - "Successfully saved active ProfileID: {}", - profile_id - ), - Err(e) => fatal_error(format!( - "Failed to save active ProfileID: {}, error: {}", - profile_id, e - )), - } - } - - pub(crate) fn save_existing_profile(&self) -> Result<()> { - self.save_profile(&self.profile()) - } - - pub(crate) fn save_profile_or_panic(&self, profile: &Profile) -> bool { - match self.save_profile(profile) { - Ok(_) => { - log::info!( - "Successfully saved profile with ID: {}", - profile.id() - ); - true - } - Err(e) => { - fatal_error(format!( - "Failed to save profile with ID: {}, error: {}", - profile.id(), - e - )); - false - } - } - } - pub(crate) fn save_new_profile_or_panic(&self, profile: &Profile) { - if self.save_profile_or_panic(profile) { - self.save_active_profile_id_or_panic(&profile.id()); - } - } -} - -#[cfg(test)] -mod tests { - use crate::prelude::*; - - #[should_panic( - expected = "Fatal error: 'Failed to save active ProfileID: 12345678-bbbb-cccc-dddd-abcd12345678, error: Unknown Error'" - )] - #[test] - fn save_active_profile_id_or_panic_fail() { - #[derive(Debug)] - struct FailSaveActiveProfileIDStorage {} - - impl SecureStorage for FailSaveActiveProfileIDStorage { - fn load_data( - &self, - _key: SecureStorageKey, - ) -> Result>> { - unreachable!() - } - - fn save_data( - &self, - key: SecureStorageKey, - _data: Vec, - ) -> Result<()> { - match key { - SecureStorageKey::ActiveProfileID => { - Err(CommonError::Unknown) - } - _ => Ok(()), - } - } - - fn delete_data_for_key( - &self, - _key: SecureStorageKey, - ) -> Result<()> { - unreachable!() - } - } - let storage = Arc::new(FailSaveActiveProfileIDStorage {}); - - _ = Wallet::by_importing_profile(Profile::sample(), storage); - } - - #[should_panic( - expected = "Fatal error: 'Failed to save profile with ID: 12345678-bbbb-cccc-dddd-abcd12345678, error: Unknown Error'" - )] - #[test] - fn save_profile_or_panic_fail() { - #[derive(Debug)] - struct FailSaveProfileStorage {} - - impl SecureStorage for FailSaveProfileStorage { - fn load_data( - &self, - _key: SecureStorageKey, - ) -> Result>> { - unreachable!() - } - - fn save_data( - &self, - key: SecureStorageKey, - _data: Vec, - ) -> Result<()> { - match key { - SecureStorageKey::ProfileSnapshot { profile_id: _ } => { - Err(CommonError::Unknown) - } - _ => Ok(()), - } - } - - fn delete_data_for_key( - &self, - _key: SecureStorageKey, - ) -> Result<()> { - unreachable!() - } - } - let storage = Arc::new(FailSaveProfileStorage {}); - - _ = Wallet::by_importing_profile(Profile::sample(), storage); - } - - #[should_panic( - expected = "Fatal error: 'Failed to save active ProfileID: ffffffff-ffff-ffff-ffff-ffffffffffff, error: Unknown Error'" - )] - #[test] - fn new_load_profile_with_id_fail() { - #[derive(Debug)] - struct FailSaveActiveProfileIDStorage {} - - impl SecureStorage for FailSaveActiveProfileIDStorage { - fn load_data( - &self, - key: SecureStorageKey, - ) -> Result>> { - match key { - SecureStorageKey::ProfileSnapshot { profile_id: _ } => { - serde_json::to_vec(&Profile::sample()) - .map(Some) - .map_err(|_| CommonError::Unknown) - } - _ => panic!("FailSaveActiveProfileIDStorage does not implement `load_data` for {:?}", key), - } - } - - fn save_data( - &self, - key: SecureStorageKey, - _data: Vec, - ) -> Result<()> { - match key { - SecureStorageKey::ActiveProfileID => { - Err(CommonError::Unknown) - } - _ => Ok(()), - } - } - - fn delete_data_for_key( - &self, - _key: SecureStorageKey, - ) -> Result<()> { - panic!("FailSaveActiveProfileIDStorage does not implement `delete_data_for_key`"); - } - } - let storage = Arc::new(FailSaveActiveProfileIDStorage {}); - - _ = Wallet::by_loading_profile_with_id(ProfileID::sample(), storage) - .unwrap(); - } -} diff --git a/src/wrapped_radix_engine_toolkit/low_level/address_conversion/mod.rs b/src/wrapped_radix_engine_toolkit/low_level/address_conversion/mod.rs deleted file mode 100644 index 9db861074..000000000 --- a/src/wrapped_radix_engine_toolkit/low_level/address_conversion/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod resource_address_from; -pub use resource_address_from::*; diff --git a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/execution_summary/mod.rs b/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/execution_summary/mod.rs deleted file mode 100644 index 92f0d8085..000000000 --- a/src/wrapped_radix_engine_toolkit/low_level/transaction_manifest/execution_summary/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod transaction_manifest_execution_summary; - -pub use transaction_manifest_execution_summary::*; diff --git a/src/wrapped_radix_engine_toolkit/mod.rs b/src/wrapped_radix_engine_toolkit/mod.rs deleted file mode 100644 index 36b4bf536..000000000 --- a/src/wrapped_radix_engine_toolkit/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod high_level; -mod low_level; -pub use high_level::*; -pub use low_level::*; diff --git a/tests/integration/main.rs b/tests/integration/main.rs index a20d367af..48d1721b1 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -1,5 +1,3 @@ -mod network_antenna_reqwest; - #[cfg(test)] mod integration_tests { @@ -9,10 +7,14 @@ mod integration_tests { use sargon::prelude::*; use std::collections::HashMap; - use crate::network_antenna_reqwest::new_gateway_client; - const MAX: Duration = Duration::from_secs(5); + #[cfg(test)] + pub fn new_gateway_client(network_id: NetworkID) -> GatewayClient { + let driver = RustNetworkingDriver::new(); + GatewayClient::new(driver, network_id) + } + #[actix_rt::test] async fn test_xrd_balance_of_account_or_zero() { let gateway_client = new_gateway_client(NetworkID::Mainnet); diff --git a/tests/vectors/main.rs b/tests/vectors/main.rs index 5560f7bac..bab560ce4 100644 --- a/tests/vectors/main.rs +++ b/tests/vectors/main.rs @@ -710,14 +710,14 @@ mod wallet_to_dapp_interaction_tests { AccountAddress::from_str("account_tdx_2_129qeystv8tufmkmjrry2g6kadhhfh4f7rd0x3t9yagcvfhspt62paz") .unwrap(), "Dff", - AppearanceID::gradient0(), + AppearanceID::new(0).unwrap(), ); let account_2 = WalletInteractionWalletAccount::new( AccountAddress::from_str("account_tdx_2_128928hvf6pjr3rx2xvdw6ulf7pc8g88ya8ma3j8dtjmntckz09fr3n") .unwrap(), "Ghhvgfvf", - AppearanceID::gradient1(), + AppearanceID::new(1).unwrap(), ); let authorized_request_response_items = WalletToDappInteractionResponseItems::AuthorizedRequest(