From 06234046604be723c897db5efac405cadbea4064 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Tue, 20 Feb 2024 13:07:14 +0100 Subject: [PATCH] WIP wrapping addresses with macro --- Cargo.lock | 1 + Cargo.toml | 1 + src/core/error/common_error.rs | 6 + .../v100/address/access_controller_address.rs | 105 ++++++ src/profile/v100/address/account_address.rs | 147 ++------ src/profile/v100/address/component_address.rs | 102 ++++++ .../v100/address/decode_address_helper.rs | 40 -- src/profile/v100/address/entity_address.rs | 41 +-- src/profile/v100/address/identity_address.rs | 345 ++++++++---------- src/profile/v100/address/mod.rs | 17 +- .../v100/address/non_fungible_global_id.rs | 26 +- src/profile/v100/address/package_address.rs | 101 +++++ src/profile/v100/address/pool_address.rs | 101 +++++ src/profile/v100/address/resource_address.rs | 50 +-- src/profile/v100/address/validator_address.rs | 101 +++++ src/profile/v100/address/vault_address.rs | 101 +++++ src/profile/v100/address/wrap_ret_address.rs | 137 +++++++ .../v100/entity/abstract_entity_type.rs | 48 +-- src/profile/v100/entity/account/account.rs | 3 +- .../third_party_deposits/asset_exception.rs | 2 - .../resource_or_non_fungible.rs | 2 - .../third_party_deposits.rs | 8 +- .../authorized_dapp/authorized_dapp.rs | 4 +- src/profile/v100/networks/profile_networks.rs | 4 +- src/sargon.udl | 26 +- src/wallet/wallet_accounts.rs | 4 +- .../dummy_types.rs | 7 - 27 files changed, 1044 insertions(+), 486 deletions(-) create mode 100644 src/profile/v100/address/access_controller_address.rs create mode 100644 src/profile/v100/address/component_address.rs delete mode 100644 src/profile/v100/address/decode_address_helper.rs create mode 100644 src/profile/v100/address/package_address.rs create mode 100644 src/profile/v100/address/pool_address.rs create mode 100644 src/profile/v100/address/validator_address.rs create mode 100644 src/profile/v100/address/vault_address.rs create mode 100644 src/profile/v100/address/wrap_ret_address.rs diff --git a/Cargo.lock b/Cargo.lock index 02cd01cd2..69482d28a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1993,6 +1993,7 @@ dependencies = [ "memoize", "num-format", "nutype", + "paste", "pretty_assertions", "pretty_env_logger", "radix-engine-common", diff --git a/Cargo.toml b/Cargo.toml index cbef79fd5..9ec601a37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,7 @@ time-util = { version = "0.3.4", features = ["chrono"] } assert-json-diff = "2.0.2" url = { version = "2.5.0", features = ["serde"] } num-format = "0.4.4" +paste = "1.0.14" [build-dependencies] uniffi = { version = "0.26.0", features = ["build"] } diff --git a/src/core/error/common_error.rs b/src/core/error/common_error.rs index cef62787b..6f8d50355 100644 --- a/src/core/error/common_error.rs +++ b/src/core/error/common_error.rs @@ -318,6 +318,12 @@ pub enum CommonError { #[error("Unrecognized Locale Identifier: {bad_value}")] UnrecognizedLocaleIdentifier { bad_value: String } = 10087, + + #[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, + } = 10088, } /* diff --git a/src/profile/v100/address/access_controller_address.rs b/src/profile/v100/address/access_controller_address.rs new file mode 100644 index 000000000..a517de783 --- /dev/null +++ b/src/profile/v100/address/access_controller_address.rs @@ -0,0 +1,105 @@ +use crate::prelude::*; + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Hash, + derive_more::FromStr, + derive_more::Display, + SerializeDisplay, + DeserializeFromStr, + uniffi::Record, +)] +#[display("{__inner}")] +pub struct AccessControllerAddress { + pub(crate) __inner: InnerAccessControllerAddress, +} + +#[uniffi::export] +pub fn new_access_controller_address( + bech32: String, +) -> Result { + AccessControllerAddress::try_from_bech32(bech32.as_str()) +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn display() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = AccessControllerAddress::try_from_bech32(s).unwrap(); + assert_eq!(format!("{a}"), s); + } + + #[test] + fn json_roundtrip() { + let a: AccessControllerAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + + assert_json_value_eq_after_roundtrip( + &a, + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"), + ); + assert_json_roundtrip(&a); + assert_json_value_ne_after_roundtrip( + &a, + json!("resource_rdx1tkk83magp3gjyxrpskfsqwkg4g949rmcjee4tu2xmw93ltw2cz94sq"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::( + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxxx") + ); + assert_json_value_fails::( + json!("account_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ); + assert_json_value_fails::(json!( + "super invalid" + )); + } + + #[test] + fn network_id_stokenet() { + let a: AccessControllerAddress = + "resource_tdx_2_1tkckx9fynl9f7756z8wxphq7wce6vk874nuq4f2nnxgh3nzrwhjdlp" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Stokenet); + } + + #[test] + fn network_id_mainnet() { + let a: AccessControllerAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Mainnet); + } +} + +#[cfg(test)] +mod uniffi_tests { + use crate::{new_resource_address, EntityAddress}; + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = AccessControllerAddress; + + #[test] + fn new() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = AccessControllerAddress::try_from_bech32(s).unwrap(); + let b = new_access_controller_address(s.to_string()).unwrap(); + assert_eq!(b.address(), s); + assert_eq!(a, b); + } +} diff --git a/src/profile/v100/address/account_address.rs b/src/profile/v100/address/account_address.rs index 3581bb934..9d026ebe5 100644 --- a/src/profile/v100/address/account_address.rs +++ b/src/profile/v100/address/account_address.rs @@ -1,103 +1,39 @@ -use radix_engine_toolkit::models::canonical_address_types::{ - CanonicalAccountAddress as RetAccountAddress, - CanonicalAddress as RetIsAddressTrait, -}; +pub use crate::prelude::*; +// use crate::InnerAccountAddress; -use crate::prelude::*; -pub trait RetStringType: - RetIsAddressTrait + std::fmt::Display + FromStr -{ -} - -/// UniFFI conversion for RET types which are DisplayFromStr using String as builtin. -impl crate::UniffiCustomTypeConverter for U { - 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() - } -} - -pub trait WrappingRetStringType: From + Into + std::fmt::Display + FromStr { - type Inner: RetStringType; -} - -impl RetStringType for RetAccountAddress {} - -pub struct InnerAccountAddress {} - -/// The address of an Account, a bech32 encoding of a public key hash -/// that starts with the prefix `"account_"`, dependent on NetworkID, meaning the same -/// public key used for two AccountAddresses on two different networks will not have -/// the same address. +/// Human readable address of an account. Always starts with `"account_"``, for example: +/// +/// `account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease` +/// +/// Most commonly the user will see this address in its abbreviated +/// form which is: +/// +/// `acco...please` +/// +/// Addresses are checksummed, as per Bech32. **Only** *Account* addresses starts with +/// the prefix `account_`. #[derive( Clone, Debug, - Default, PartialEq, Eq, Hash, - // SerializeDisplay, - // DeserializeFromStr, + derive_more::FromStr, derive_more::Display, - uniffi::Record, -)] -#[display("{__inner}")] -struct AccountAddress2 { - __inner: RetAccountAddress, -} - -impl WrappingRetStringType for AccountAddress2 { - type Inner = RetAccountAddress; -} - -/// The address of an Account, a bech32 encoding of a public key hash -/// that starts with the prefix `"account_"`, dependent on NetworkID, meaning the same -/// public key used for two AccountAddresses on two different networks will not have -/// the same address. -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Hash, SerializeDisplay, DeserializeFromStr, - derive_more::Display, uniffi::Record, )] -#[display("{address}")] +#[display("{__inner}")] pub struct AccountAddress { - /// Human readable address of an account. Always starts with `"account_"``, for example: - /// - /// `account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease` - /// - /// Most commonly the user will see this address in its abbreviated - /// form which is: - /// - /// `acco...please` - /// - /// Addresses are checksummed, as per Bech32. **Only** *Account* addresses starts with - /// the prefix `account_`. - pub address: String, - - /// The network this account address is tied to, i.e. which was used when a public key - /// hash was used to bech32 encode it. This means that two public key hashes will result - /// in two different account address on two different networks. - pub network_id: NetworkID, + pub(crate) __inner: InnerAccountAddress, } #[uniffi::export] pub fn new_account_address(bech32: String) -> Result { - AccountAddress::try_from_bech32(bech32.as_str()) + // AccountAddress::try_from_bech32(bech32.as_str()) + todo!() } #[uniffi::export] @@ -127,7 +63,8 @@ pub fn account_address_to_short(address: &AccountAddress) -> String { impl AccountAddress { pub fn new(public_key: PublicKey, network_id: NetworkID) -> Self { - ::from_public_key(public_key, network_id) + // ::from_public_key(public_key, network_id) + todo!(); } /// Formats the AccountAddress to its abbreviated form which is what the user @@ -143,16 +80,8 @@ impl AccountAddress { /// `account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease` /// pub fn short(&self) -> String { - let suffix = suffix_str(6, &self.address); - format!("{}...{}", &self.address[0..4], suffix) - } -} - -impl FromStr for AccountAddress { - type Err = CommonError; - - fn from_str(s: &str) -> Result { - AccountAddress::try_from_bech32(s) + let suffix = suffix_str(6, &self.address()); + format!("{}...{}", &self.address()[0..4], suffix) } } @@ -163,20 +92,6 @@ impl EntityAddress for AccountAddress { fn entity_type() -> AbstractEntityType { AbstractEntityType::Account } - - // Underscored to decrease visibility. You SHOULD NOT call this function directly, - // instead use `try_from_bech32` which performs proper validation. Impl types SHOULD - // `panic` if `address` does not start with `Self::entity_type().hrp()` - fn __with_address_and_network_id( - address: &str, - network_id: NetworkID, - ) -> Self { - assert!(address.starts_with(&Self::entity_type().hrp()), "Invalid address, you SHOULD NOT call this function directly, you should use `try_from_bech32` instead."); - Self { - address: address.to_string(), - network_id, - } - } } impl HasPlaceholder for AccountAddress { @@ -198,7 +113,7 @@ impl AccountAddress { "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease", ) .unwrap(); - assert_eq!(address.network_id, NetworkID::Mainnet); + assert_eq!(address.network_id(), NetworkID::Mainnet); address } @@ -208,7 +123,7 @@ impl AccountAddress { "account_rdx16yf8jxxpdtcf4afpj5ddeuazp2evep7quuhgtq28vjznee08master", ) .unwrap(); - assert_eq!(address.network_id, NetworkID::Mainnet); + assert_eq!(address.network_id(), NetworkID::Mainnet); address } @@ -218,7 +133,7 @@ impl AccountAddress { "account_tdx_2_1289zm062j788dwrjefqkfgfeea5tkkdnh8htqhdrzdvjkql4kxceql", ) .unwrap(); - assert_eq!(address.network_id, NetworkID::Stokenet); + assert_eq!(address.network_id(), NetworkID::Stokenet); address } @@ -228,7 +143,7 @@ impl AccountAddress { "account_tdx_2_129663ef7fj8azge3y6sl73lf9vyqt53ewzlf7ul2l76mg5wyqlqlpr", ) .unwrap(); - assert_eq!(address.network_id, NetworkID::Stokenet); + assert_eq!(address.network_id(), NetworkID::Stokenet); address } } @@ -297,7 +212,7 @@ mod tests { assert_eq!( AccountAddress::from_public_key::(public_key.into(), NetworkID::Mainnet) - .address, + .address(), "account_rdx129qdd2yp9vs8jkkn2uwn6sw0ejwmcwr3r4c3usr2hp0nau67m2kzdm" ) } @@ -310,7 +225,7 @@ mod tests { .unwrap(); assert_eq!( - AccountAddress::new(public_key.into(), NetworkID::Mainnet).address, + AccountAddress::new(public_key.into(), NetworkID::Mainnet).address(), "account_rdx129qdd2yp9vs8jkkn2uwn6sw0ejwmcwr3r4c3usr2hp0nau67m2kzdm" ) } @@ -321,7 +236,7 @@ mod tests { "account_tdx_b_1286wrrqrfcrfhthfrtdywe8alney8zu0ja5xrhcq2475ej08m9raqq", ) .unwrap(); - assert_eq!(address.network_id, NetworkID::Nebunet) + assert_eq!(address.network_id(), NetworkID::Nebunet) } #[test] @@ -330,7 +245,7 @@ mod tests { "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease", ) .unwrap(); - assert_eq!(sut.network_id, NetworkID::Mainnet); + assert_eq!(sut.network_id(), NetworkID::Mainnet); } #[test] @@ -443,6 +358,6 @@ mod uniffi_tests { AccountAddress::try_from_bech32(bech32).unwrap(), from_bech32.clone() ); - assert_eq!(from_bech32.address, bech32) + assert_eq!(from_bech32.address(), bech32) } } diff --git a/src/profile/v100/address/component_address.rs b/src/profile/v100/address/component_address.rs new file mode 100644 index 000000000..f6947aa27 --- /dev/null +++ b/src/profile/v100/address/component_address.rs @@ -0,0 +1,102 @@ +use crate::prelude::*; + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Hash, + derive_more::FromStr, + derive_more::Display, + SerializeDisplay, + DeserializeFromStr, + uniffi::Record, +)] +#[display("{__inner}")] +pub struct ComponentAddress { + pub(crate) __inner: InnerComponentAddress, +} + +#[uniffi::export] +pub fn new_component_address(bech32: String) -> Result { + // ComponentAddress::try_from_bech32(bech32.as_str()) + todo!() +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn display() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = ComponentAddress::try_from_bech32(s).unwrap(); + assert_eq!(format!("{a}"), s); + } + + #[test] + fn json_roundtrip() { + let a: ComponentAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + + assert_json_value_eq_after_roundtrip( + &a, + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"), + ); + assert_json_roundtrip(&a); + assert_json_value_ne_after_roundtrip( + &a, + json!("resource_rdx1tkk83magp3gjyxrpskfsqwkg4g949rmcjee4tu2xmw93ltw2cz94sq"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::( + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxxx") + ); + assert_json_value_fails::( + json!("account_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ); + assert_json_value_fails::(json!("super invalid")); + } + + #[test] + fn network_id_stokenet() { + let a: ComponentAddress = + "resource_tdx_2_1tkckx9fynl9f7756z8wxphq7wce6vk874nuq4f2nnxgh3nzrwhjdlp" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Stokenet); + } + + #[test] + fn network_id_mainnet() { + let a: ComponentAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Mainnet); + } +} + +#[cfg(test)] +mod uniffi_tests { + use crate::{new_resource_address, EntityAddress}; + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = ComponentAddress; + + #[test] + fn new() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = ComponentAddress::try_from_bech32(s).unwrap(); + let b = new_component_address(s.to_string()).unwrap(); + assert_eq!(b.address(), s); + assert_eq!(a, b); + } +} diff --git a/src/profile/v100/address/decode_address_helper.rs b/src/profile/v100/address/decode_address_helper.rs deleted file mode 100644 index 910eb735f..000000000 --- a/src/profile/v100/address/decode_address_helper.rs +++ /dev/null @@ -1,40 +0,0 @@ -use radix_engine_common::types::EntityType as ScryptoEntityType; -use radix_engine_toolkit::functions::address::decode; - -use crate::prelude::*; -type EngineDecodeAddressOutput = (u8, ScryptoEntityType, String, [u8; 30]); -pub type DecodeAddressOutput = - (NetworkID, AbstractEntityType, String, [u8; 30]); - -fn engine_decode_address(s: &str) -> Result { - let Some(tuple) = decode(s) else { - return Err(CommonError::FailedToDecodeAddressFromBech32 { - bad_value: s.to_owned(), - }); - }; - Ok(tuple) -} - -pub fn decode_address(s: &str) -> Result { - let (network_id_raw, entity_type_engine, hrp, data) = - engine_decode_address(s)?; - let entity_type = AbstractEntityType::try_from(entity_type_engine)?; - let network_id = NetworkID::try_from(network_id_raw)?; - Ok((network_id, entity_type, hrp, data)) -} - -#[cfg(test)] -mod tests { - - use crate::prelude::*; - - #[test] - fn decode_unsupported_entity() { - assert_eq!( - decode_address( - "consensusmanager_rdx1scxxxxxxxxxxcnsmgrxxxxxxxxx000999665565xxxxxxxxxcnsmgr" - ), - Err(CommonError::UnsupportedEntityType) - ); - } -} diff --git a/src/profile/v100/address/entity_address.rs b/src/profile/v100/address/entity_address.rs index 381618deb..8982cbfd4 100644 --- a/src/profile/v100/address/entity_address.rs +++ b/src/profile/v100/address/entity_address.rs @@ -1,7 +1,6 @@ use crate::prelude::*; use radix_engine_common::{ address::AddressBech32Encoder, crypto::PublicKey as ScryptoPublicKey, - types::NodeId as ScryptoNodeId, }; use radix_engine_toolkit::functions::derive::{ virtual_account_address_from_public_key as RET_new_account_address, @@ -10,28 +9,9 @@ use radix_engine_toolkit::functions::derive::{ /// An address of an entity, provides default implementation of `try_from_bech32` /// to decode a bech32 encoded address string into Self. -pub trait EntityAddress: Sized { +pub trait EntityAddress: AddressViaRet { fn entity_type() -> AbstractEntityType; - // Underscored to decrease visibility. You SHOULD NOT call this function directly, - // instead use `try_from_bech32` which performs proper validation. Impl types SHOULD - // `panic` if `address` does not start with `Self::entity_type().hrp()` - fn __with_address_and_network_id( - address: &str, - network_id: NetworkID, - ) -> Self; - - fn address_from_node_id( - node_id: ScryptoNodeId, - network_id: NetworkID, - ) -> String { - let bech32_encoder = - AddressBech32Encoder::new(&network_id.network_definition()); - bech32_encoder - .encode(node_id.as_bytes()) - .expect("Should always be able to format address") - } - /// Creates a new address from `public_key` and `network_id` by bech32 encoding /// it. #[cfg(not(tarpaulin_include))] // false negative @@ -44,12 +24,11 @@ pub trait EntityAddress: Sized { AbstractEntityType::Identity => { RET_new_identity_address(&public_key) } - AbstractEntityType::Resource => panic!("resource"), }; - let address = - Self::address_from_node_id(component.into_node_id(), network_id); - Self::__with_address_and_network_id(&address, network_id) + let node_id = component.into_node_id(); + + 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 @@ -69,16 +48,4 @@ pub trait EntityAddress: Sized { network_id, ) } - - #[cfg(not(tarpaulin_include))] // false negative - fn try_from_bech32(s: &str) -> Result { - let (network_id, entity_type, hrp, _) = decode_address(s)?; - if entity_type != Self::entity_type() { - return Err(CommonError::MismatchingEntityTypeWhileDecodingAddress); - } - - assert!(hrp.starts_with(&entity_type.hrp()), "Mismatching HRP while decoding address, this should never happen. Did internal function `decode_address` change? Or did you accidentally change or impl the `hrp` method on EntityType?"); - - Ok(Self::__with_address_and_network_id(s, network_id)) - } } diff --git a/src/profile/v100/address/identity_address.rs b/src/profile/v100/address/identity_address.rs index 8e9dfbff6..c596c5d2f 100644 --- a/src/profile/v100/address/identity_address.rs +++ b/src/profile/v100/address/identity_address.rs @@ -1,37 +1,27 @@ use crate::prelude::*; -/// The address of an identity, used by Personas, a bech32 encoding of a public key hash -/// that starts with the prefix `"identity_"`, dependent on NetworkID, meaning the same -/// public key used for two IdentityAddresses on two different networks will not have -/// the same address. +/// Human readable address of an identity, which are used by Personas. Always starts with +/// the prefix `"identity_"`, for example: +/// +/// `identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j` +/// +/// Addresses are checksummed, as per Bech32. **Only** *Identity* addresses starts with +/// the prefix `"identity_"`. #[derive( Clone, Debug, PartialEq, Eq, Hash, - PartialOrd, - Ord, + derive_more::FromStr, + derive_more::Display, SerializeDisplay, DeserializeFromStr, - derive_more::Display, uniffi::Record, )] -#[display("{address}")] +#[display("{__inner}")] pub struct IdentityAddress { - /// Human readable address of an identity, which are used by Personas. Always starts with - /// the prefix `"identity_"`, for example: - /// - /// `identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j` - /// - /// Addresses are checksummed, as per Bech32. **Only** *Identity* addresses starts with - /// the prefix `"identity_"`. - pub address: String, - - /// The network this identity address is tied to, i.e. which was used when a public key - /// hash was used to bech32 encode it. This means that two public key hashes will result - /// in two different identity address on two different networks. - pub network_id: NetworkID, + pub(crate) __inner: InnerIdentityAddress, } impl EntityAddress for IdentityAddress { @@ -41,20 +31,6 @@ impl EntityAddress for IdentityAddress { fn entity_type() -> AbstractEntityType { AbstractEntityType::Identity } - - // Underscored to decrease visibility. You SHOULD NOT call this function directly, - // instead use `try_from_bech32` which performs proper validation. Impl types SHOULD - // `panic` if `address` does not start with `Self::entity_type().hrp()` - fn __with_address_and_network_id( - address: &str, - network_id: NetworkID, - ) -> Self { - assert!(address.starts_with(&Self::entity_type().hrp()), "Invalid address, you SHOULD NOT call this function directly, you should use `try_from_bech32` instead."); - Self { - address: address.to_string(), - network_id, - } - } } impl IdentityAddress { @@ -62,28 +38,28 @@ impl IdentityAddress { let address: IdentityAddress = "identity_rdx122kttqch0eehzj6f9nkkxcw7msfeg9udurq5u0ysa0e92c59w0mg6x" .parse() .expect("Should have a valid placeholder value"); - assert_eq!(address.network_id, NetworkID::Mainnet); + assert_eq!(address.network_id(), NetworkID::Mainnet); address } pub fn placeholder_mainnet_other() -> Self { let address: IdentityAddress = "identity_rdx12gcd4r799jpvztlffgw483pqcen98pjnay988n8rmscdswd872xy62" .parse() .expect("Should have a valid placeholder value"); - assert_eq!(address.network_id, NetworkID::Mainnet); + assert_eq!(address.network_id(), NetworkID::Mainnet); address } pub fn placeholder_stokenet() -> Self { let address: IdentityAddress = "identity_tdx_2_12fk6qyu2860xyx2jk7j6ex464ccrnxrve4kpaa8qyxx99y5627ahhc" .parse() .expect("Should have a valid placeholder value"); - assert_eq!(address.network_id, NetworkID::Stokenet); + assert_eq!(address.network_id(), NetworkID::Stokenet); address } pub fn placeholder_stokenet_other() -> Self { let address: IdentityAddress = "identity_tdx_2_12gr0d9da3jvye7mdrreljyqs35esjyjsl9r8t5v96hq6fq367cln08" .parse() .expect("Should have a valid placeholder value"); - assert_eq!(address.network_id, NetworkID::Stokenet); + assert_eq!(address.network_id(), NetworkID::Stokenet); address } } @@ -98,153 +74,146 @@ impl HasPlaceholder for IdentityAddress { } } -impl FromStr for IdentityAddress { - type Err = CommonError; - - fn from_str(s: &str) -> Result { - IdentityAddress::try_from_bech32(s) - } -} - -#[cfg(test)] -mod tests { - use crate::prelude::*; - use radix_engine_common::crypto::{ - Ed25519PublicKey as ScryptoEd25519PublicKey, - PublicKey as ScryptoPublicKey, - }; - - #[test] - fn from_bech32() { - assert!(IdentityAddress::try_from_bech32( - "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j", - ) - .is_ok()); - } - - #[test] - fn from_str() { - assert!( - "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j" - .parse::() - .is_ok() - ); - } - - #[test] - fn display() { - let a = IdentityAddress::try_from_bech32( - "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j", - ) - .unwrap(); - assert_eq!( - format!("{}", a), - "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j" - ); - } - - #[test] - fn from_public_key_bytes_and_network_id() { - let public_key = ScryptoEd25519PublicKey::from_str( - "6c28952be5cdade99c7dd5d003b6b692714b6b74c5fdb5fdc9a8e4ee1d297838", - ) - .unwrap(); - assert_eq!( - IdentityAddress::from_public_key( - ScryptoPublicKey::Ed25519(public_key), - NetworkID::Mainnet - ) - .address, - "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j" - ) - } - #[test] - fn network_id() { - let sut = IdentityAddress::try_from_bech32( - "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j", - ) - .unwrap(); - assert_eq!(sut.network_id, NetworkID::Mainnet); - } - - #[test] - fn equality() { - assert_eq!( - IdentityAddress::placeholder(), - IdentityAddress::placeholder() - ); - assert_eq!( - IdentityAddress::placeholder_other(), - IdentityAddress::placeholder_other() - ); - } - - #[test] - fn inequality() { - assert_ne!( - IdentityAddress::placeholder(), - IdentityAddress::placeholder_other() - ); - } - - #[test] - fn invalid() { - assert_eq!( - IdentityAddress::try_from_bech32("x"), - Err(CommonError::FailedToDecodeAddressFromBech32 { - bad_value: "x".to_owned() - }) - ) - } - - #[test] - fn invalid_checksum() { - let s = "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8x"; - assert_eq!( - IdentityAddress::try_from_bech32(s), - Err(CommonError::FailedToDecodeAddressFromBech32 { - bad_value: s.to_owned() - }) - ) - } - - #[test] - fn invalid_entity_type() { - assert_eq!( - IdentityAddress::try_from_bech32( - "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" - ), - Err(CommonError::MismatchingEntityTypeWhileDecodingAddress) - ) - } - - #[test] - fn json_roundtrip_success() { - let a: IdentityAddress = - "identity_rdx12gzxlgre0glhh9jxaptm7tdth8j4w4r8ykpg2xjfv45nghzsjzrvmp" - .parse() - .unwrap(); - - assert_json_value_eq_after_roundtrip( - &a, - json!("identity_rdx12gzxlgre0glhh9jxaptm7tdth8j4w4r8ykpg2xjfv45nghzsjzrvmp"), - ); - assert_json_roundtrip(&a); - assert_json_value_ne_after_roundtrip( - &a, - json!("identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j"), - ); - } - - #[test] - fn json_roundtrip_fails_for_invalid() { - assert_json_value_fails::( - json!("account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease") - ); - assert_json_value_fails::( - json!("identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzkuxx") - ); - assert_json_value_fails::(json!("super invalid")); - } -} +// #[cfg(test)] +// mod tests { +// use crate::prelude::*; +// use radix_engine_common::crypto::{ +// Ed25519PublicKey as ScryptoEd25519PublicKey, +// PublicKey as ScryptoPublicKey, +// }; + +// #[test] +// fn from_bech32() { +// assert!(IdentityAddress::try_from_bech32( +// "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j", +// ) +// .is_ok()); +// } + +// #[test] +// fn from_str() { +// assert!( +// "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j" +// .parse::() +// .is_ok() +// ); +// } + +// #[test] +// fn display() { +// let a = IdentityAddress::try_from_bech32( +// "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j", +// ) +// .unwrap(); +// assert_eq!( +// format!("{}", a), +// "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j" +// ); +// } + +// #[test] +// fn from_public_key_bytes_and_network_id() { +// let public_key = ScryptoEd25519PublicKey::from_str( +// "6c28952be5cdade99c7dd5d003b6b692714b6b74c5fdb5fdc9a8e4ee1d297838", +// ) +// .unwrap(); +// assert_eq!( +// IdentityAddress::from_public_key( +// ScryptoPublicKey::Ed25519(public_key), +// NetworkID::Mainnet +// ) +// .address, +// "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j" +// ) +// } + +// #[test] +// fn network_id() { +// let sut = IdentityAddress::try_from_bech32( +// "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j", +// ) +// .unwrap(); +// assert_eq!(sut.network_id, NetworkID::Mainnet); +// } + +// #[test] +// fn equality() { +// assert_eq!( +// IdentityAddress::placeholder(), +// IdentityAddress::placeholder() +// ); +// assert_eq!( +// IdentityAddress::placeholder_other(), +// IdentityAddress::placeholder_other() +// ); +// } + +// #[test] +// fn inequality() { +// assert_ne!( +// IdentityAddress::placeholder(), +// IdentityAddress::placeholder_other() +// ); +// } + +// #[test] +// fn invalid() { +// assert_eq!( +// IdentityAddress::try_from_bech32("x"), +// Err(CommonError::FailedToDecodeAddressFromBech32 { +// bad_value: "x".to_owned() +// }) +// ) +// } + +// #[test] +// fn invalid_checksum() { +// let s = "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8x"; +// assert_eq!( +// IdentityAddress::try_from_bech32(s), +// Err(CommonError::FailedToDecodeAddressFromBech32 { +// bad_value: s.to_owned() +// }) +// ) +// } + +// #[test] +// fn invalid_entity_type() { +// assert_eq!( +// IdentityAddress::try_from_bech32( +// "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" +// ), +// Err(CommonError::MismatchingEntityTypeWhileDecodingAddress) +// ) +// } + +// #[test] +// fn json_roundtrip_success() { +// let a: IdentityAddress = +// "identity_rdx12gzxlgre0glhh9jxaptm7tdth8j4w4r8ykpg2xjfv45nghzsjzrvmp" +// .parse() +// .unwrap(); + +// assert_json_value_eq_after_roundtrip( +// &a, +// json!("identity_rdx12gzxlgre0glhh9jxaptm7tdth8j4w4r8ykpg2xjfv45nghzsjzrvmp"), +// ); +// assert_json_roundtrip(&a); +// assert_json_value_ne_after_roundtrip( +// &a, +// json!("identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j"), +// ); +// } + +// #[test] +// fn json_roundtrip_fails_for_invalid() { +// assert_json_value_fails::( +// json!("account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease") +// ); +// assert_json_value_fails::( +// json!("identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzkuxx") +// ); +// assert_json_value_fails::(json!("super invalid")); +// } +// } diff --git a/src/profile/v100/address/mod.rs b/src/profile/v100/address/mod.rs index 5fb63d18b..b38c92e63 100644 --- a/src/profile/v100/address/mod.rs +++ b/src/profile/v100/address/mod.rs @@ -1,15 +1,28 @@ +mod access_controller_address; mod account_address; -mod decode_address_helper; +mod component_address; mod entity_address; mod identity_address; mod non_fungible_global_id; mod non_fungible_local_id; +mod package_address; +mod pool_address; mod resource_address; +mod validator_address; +mod vault_address; +mod wrap_ret_address; +pub use access_controller_address::*; pub use account_address::*; -pub use decode_address_helper::*; +pub use component_address::*; + pub use entity_address::*; pub use identity_address::*; pub use non_fungible_global_id::*; pub use non_fungible_local_id::*; +pub use package_address::*; +pub use pool_address::*; pub use resource_address::*; +pub use validator_address::*; +pub use vault_address::*; +pub use wrap_ret_address::*; diff --git a/src/profile/v100/address/non_fungible_global_id.rs b/src/profile/v100/address/non_fungible_global_id.rs index 59881672b..8faa2b465 100644 --- a/src/profile/v100/address/non_fungible_global_id.rs +++ b/src/profile/v100/address/non_fungible_global_id.rs @@ -27,10 +27,12 @@ pub struct NonFungibleGlobalId { impl From for radix_engine_common::types::ResourceAddress { fn from(value: ResourceAddress) -> Self { radix_engine_common::types::ResourceAddress::try_from_bech32( - &AddressBech32Decoder::new(&value.network_id.network_definition()), - value.address.clone().as_str(), + &AddressBech32Decoder::new( + &value.network_id().network_definition(), + ), + value.address().clone().as_str(), ) - .unwrap() + .expect("Should always be able to convert from Sargon to RET for ResourceAddress") } } @@ -43,23 +45,23 @@ impl NonFungibleGlobalId { .network_id .try_into() .expect("should be able to know network"); - let resource_address_bech32 = ResourceAddress::address_from_node_id( + + let non_fungible_local_id: NonFungibleLocalId = engine_local_id.into(); + + let resource_address = ResourceAddress::new( engine_resource_address.into_node_id(), network_id, - ); + ) + .expect("Should always be able to convert between Sargon and RET"); - let non_fungible_local_id: NonFungibleLocalId = engine_local_id.into(); Self { - resource_address: ResourceAddress { - address: resource_address_bech32, - network_id: NetworkID::from_repr(internal.network_id).unwrap(), - }, + resource_address, non_fungible_local_id, } } fn network_id(&self) -> NetworkID { - self.resource_address.network_id + self.resource_address.network_id() } fn engine(&self) -> RETNonFungibleGlobalId { @@ -139,7 +141,7 @@ mod tests { let str = "resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd:#2244#"; let id: NonFungibleGlobalId = str.parse().unwrap(); assert_eq!( - id.resource_address.address, + id.resource_address.address(), "resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd" ); } diff --git a/src/profile/v100/address/package_address.rs b/src/profile/v100/address/package_address.rs new file mode 100644 index 000000000..c8d9ff3a7 --- /dev/null +++ b/src/profile/v100/address/package_address.rs @@ -0,0 +1,101 @@ +use crate::prelude::*; + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Hash, + derive_more::FromStr, + derive_more::Display, + SerializeDisplay, + DeserializeFromStr, + uniffi::Record, +)] +#[display("{__inner}")] +pub struct PackageAddress { + pub(crate) __inner: InnerPackageAddress, +} + +#[uniffi::export] +pub fn new_package_address(bech32: String) -> Result { + PackageAddress::try_from_bech32(bech32.as_str()) +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn display() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = PackageAddress::try_from_bech32(s).unwrap(); + assert_eq!(format!("{a}"), s); + } + + #[test] + fn json_roundtrip() { + let a: PackageAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + + assert_json_value_eq_after_roundtrip( + &a, + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"), + ); + assert_json_roundtrip(&a); + assert_json_value_ne_after_roundtrip( + &a, + json!("resource_rdx1tkk83magp3gjyxrpskfsqwkg4g949rmcjee4tu2xmw93ltw2cz94sq"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::( + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxxx") + ); + assert_json_value_fails::( + json!("account_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ); + assert_json_value_fails::(json!("super invalid")); + } + + #[test] + fn network_id_stokenet() { + let a: PackageAddress = + "resource_tdx_2_1tkckx9fynl9f7756z8wxphq7wce6vk874nuq4f2nnxgh3nzrwhjdlp" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Stokenet); + } + + #[test] + fn network_id_mainnet() { + let a: PackageAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Mainnet); + } +} + +#[cfg(test)] +mod uniffi_tests { + use crate::{new_resource_address, EntityAddress}; + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PackageAddress; + + #[test] + fn new() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = PackageAddress::try_from_bech32(s).unwrap(); + let b = new_package_address(s.to_string()).unwrap(); + assert_eq!(b.address(), s); + assert_eq!(a, b); + } +} diff --git a/src/profile/v100/address/pool_address.rs b/src/profile/v100/address/pool_address.rs new file mode 100644 index 000000000..ffa16e417 --- /dev/null +++ b/src/profile/v100/address/pool_address.rs @@ -0,0 +1,101 @@ +use crate::prelude::*; + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Hash, + derive_more::FromStr, + derive_more::Display, + SerializeDisplay, + DeserializeFromStr, + uniffi::Record, +)] +#[display("{__inner}")] +pub struct PoolAddress { + pub(crate) __inner: InnerPoolAddress, +} + +#[uniffi::export] +pub fn new_pool_address(bech32: String) -> Result { + PoolAddress::try_from_bech32(bech32.as_str()) +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn display() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = PoolAddress::try_from_bech32(s).unwrap(); + assert_eq!(format!("{a}"), s); + } + + #[test] + fn json_roundtrip() { + let a: PoolAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + + assert_json_value_eq_after_roundtrip( + &a, + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"), + ); + assert_json_roundtrip(&a); + assert_json_value_ne_after_roundtrip( + &a, + json!("resource_rdx1tkk83magp3gjyxrpskfsqwkg4g949rmcjee4tu2xmw93ltw2cz94sq"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::( + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxxx") + ); + assert_json_value_fails::( + json!("account_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ); + assert_json_value_fails::(json!("super invalid")); + } + + #[test] + fn network_id_stokenet() { + let a: PoolAddress = + "resource_tdx_2_1tkckx9fynl9f7756z8wxphq7wce6vk874nuq4f2nnxgh3nzrwhjdlp" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Stokenet); + } + + #[test] + fn network_id_mainnet() { + let a: PoolAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Mainnet); + } +} + +#[cfg(test)] +mod uniffi_tests { + use crate::{new_resource_address, EntityAddress}; + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PoolAddress; + + #[test] + fn new() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = PoolAddress::try_from_bech32(s).unwrap(); + let b = new_pool_address(s.to_string()).unwrap(); + assert_eq!(b.address(), s); + assert_eq!(a, b); + } +} diff --git a/src/profile/v100/address/resource_address.rs b/src/profile/v100/address/resource_address.rs index ec9c60155..6239f7511 100644 --- a/src/profile/v100/address/resource_address.rs +++ b/src/profile/v100/address/resource_address.rs @@ -7,21 +7,18 @@ use crate::prelude::*; #[derive( Clone, Debug, - Default, PartialEq, Eq, Hash, - PartialOrd, - Ord, + derive_more::FromStr, + derive_more::Display, SerializeDisplay, DeserializeFromStr, - derive_more::Display, uniffi::Record, )] -#[display("{address}")] +#[display("{__inner}")] pub struct ResourceAddress { - pub address: String, - pub network_id: NetworkID, + pub(crate) __inner: InnerResourceAddress, } #[uniffi::export] @@ -29,34 +26,6 @@ pub fn new_resource_address(bech32: String) -> Result { ResourceAddress::try_from_bech32(bech32.as_str()) } -impl EntityAddress for ResourceAddress { - fn entity_type() -> AbstractEntityType { - AbstractEntityType::Resource - } - - // Underscored to decrease visibility. You SHOULD NOT call this function directly, - // instead use `try_from_bech32` which performs proper validation. Impl types SHOULD - // `panic` if `address` does not start with `Self::entity_type().hrp()` - fn __with_address_and_network_id( - address: &str, - network_id: NetworkID, - ) -> Self { - assert!(address.starts_with(&Self::entity_type().hrp()), "Invalid address, you SHOULD NOT call this function directly, you should use `try_from_bech32` instead."); - Self { - address: address.to_string(), - network_id, - } - } -} - -impl FromStr for ResourceAddress { - type Err = CommonError; - - fn from_str(s: &str) -> Result { - ResourceAddress::try_from_bech32(s) - } -} - #[cfg(test)] mod tests { use crate::prelude::*; @@ -103,7 +72,7 @@ mod tests { "resource_tdx_2_1tkckx9fynl9f7756z8wxphq7wce6vk874nuq4f2nnxgh3nzrwhjdlp" .parse() .unwrap(); - assert_eq!(a.network_id, NetworkID::Stokenet); + assert_eq!(a.network_id(), NetworkID::Stokenet); } #[test] @@ -112,7 +81,7 @@ mod tests { "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" .parse() .unwrap(); - assert_eq!(a.network_id, NetworkID::Mainnet); + assert_eq!(a.network_id(), NetworkID::Mainnet); } } @@ -120,14 +89,17 @@ mod tests { mod uniffi_tests { use crate::{new_resource_address, EntityAddress}; - use super::ResourceAddress; + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = ResourceAddress; #[test] fn new() { let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; let a = ResourceAddress::try_from_bech32(s).unwrap(); let b = new_resource_address(s.to_string()).unwrap(); - assert_eq!(b.address, s); + assert_eq!(b.address(), s); assert_eq!(a, b); } } diff --git a/src/profile/v100/address/validator_address.rs b/src/profile/v100/address/validator_address.rs new file mode 100644 index 000000000..17c2a5db5 --- /dev/null +++ b/src/profile/v100/address/validator_address.rs @@ -0,0 +1,101 @@ +use crate::prelude::*; + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Hash, + derive_more::FromStr, + derive_more::Display, + SerializeDisplay, + DeserializeFromStr, + uniffi::Record, +)] +#[display("{__inner}")] +pub struct ValidatorAddress { + pub(crate) __inner: InnerValidatorAddress, +} + +#[uniffi::export] +pub fn new_validator_address(bech32: String) -> Result { + ValidatorAddress::try_from_bech32(bech32.as_str()) +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn display() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = ValidatorAddress::try_from_bech32(s).unwrap(); + assert_eq!(format!("{a}"), s); + } + + #[test] + fn json_roundtrip() { + let a: ValidatorAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + + assert_json_value_eq_after_roundtrip( + &a, + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"), + ); + assert_json_roundtrip(&a); + assert_json_value_ne_after_roundtrip( + &a, + json!("resource_rdx1tkk83magp3gjyxrpskfsqwkg4g949rmcjee4tu2xmw93ltw2cz94sq"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::( + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxxx") + ); + assert_json_value_fails::( + json!("account_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ); + assert_json_value_fails::(json!("super invalid")); + } + + #[test] + fn network_id_stokenet() { + let a: ValidatorAddress = + "resource_tdx_2_1tkckx9fynl9f7756z8wxphq7wce6vk874nuq4f2nnxgh3nzrwhjdlp" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Stokenet); + } + + #[test] + fn network_id_mainnet() { + let a: ValidatorAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Mainnet); + } +} + +#[cfg(test)] +mod uniffi_tests { + use crate::{new_resource_address, EntityAddress}; + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = ValidatorAddress; + + #[test] + fn new() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = ValidatorAddress::try_from_bech32(s).unwrap(); + let b = new_validator_address(s.to_string()).unwrap(); + assert_eq!(b.address(), s); + assert_eq!(a, b); + } +} diff --git a/src/profile/v100/address/vault_address.rs b/src/profile/v100/address/vault_address.rs new file mode 100644 index 000000000..921d44cf7 --- /dev/null +++ b/src/profile/v100/address/vault_address.rs @@ -0,0 +1,101 @@ +use crate::prelude::*; + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Hash, + derive_more::FromStr, + derive_more::Display, + SerializeDisplay, + DeserializeFromStr, + uniffi::Record, +)] +#[display("{__inner}")] +pub struct VaultAddress { + pub(crate) __inner: InnerVaultAddress, +} + +#[uniffi::export] +pub fn new_vault_address(bech32: String) -> Result { + VaultAddress::try_from_bech32(bech32.as_str()) +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn display() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = VaultAddress::try_from_bech32(s).unwrap(); + assert_eq!(format!("{a}"), s); + } + + #[test] + fn json_roundtrip() { + let a: VaultAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + + assert_json_value_eq_after_roundtrip( + &a, + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"), + ); + assert_json_roundtrip(&a); + assert_json_value_ne_after_roundtrip( + &a, + json!("resource_rdx1tkk83magp3gjyxrpskfsqwkg4g949rmcjee4tu2xmw93ltw2cz94sq"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::( + json!("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxxx") + ); + assert_json_value_fails::( + json!("account_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ); + assert_json_value_fails::(json!("super invalid")); + } + + #[test] + fn network_id_stokenet() { + let a: VaultAddress = + "resource_tdx_2_1tkckx9fynl9f7756z8wxphq7wce6vk874nuq4f2nnxgh3nzrwhjdlp" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Stokenet); + } + + #[test] + fn network_id_mainnet() { + let a: VaultAddress = + "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd" + .parse() + .unwrap(); + assert_eq!(a.network_id(), NetworkID::Mainnet); + } +} + +#[cfg(test)] +mod uniffi_tests { + use crate::{new_resource_address, EntityAddress}; + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = VaultAddress; + + #[test] + fn new() { + let s = "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd"; + let a = VaultAddress::try_from_bech32(s).unwrap(); + let b = new_vault_address(s.to_string()).unwrap(); + assert_eq!(b.address(), s); + assert_eq!(a, b); + } +} diff --git a/src/profile/v100/address/wrap_ret_address.rs b/src/profile/v100/address/wrap_ret_address.rs new file mode 100644 index 000000000..bc9d744ac --- /dev/null +++ b/src/profile/v100/address/wrap_ret_address.rs @@ -0,0 +1,137 @@ +use crate::prelude::*; +use paste::*; +use radix_engine_common::types::{ + EntityType as ScryptoEntityType, NodeId as ScryptoNodeId, +}; +use radix_engine_toolkit::models::canonical_address_types::{ + CanonicalAccessControllerAddress as RetAccessControllerAddress, + CanonicalAccountAddress as RetAccountAddress, + CanonicalAddress as RetIsAddressTrait, + CanonicalAddressError as RetCanonicalAddressError, + CanonicalComponentAddress as RetComponentAddress, + CanonicalIdentityAddress as RetIdentityAddress, + CanonicalPackageAddress as RetPackageAddress, + CanonicalPoolAddress as RetPoolAddress, + CanonicalResourceAddress as RetResourceAddress, + CanonicalValidatorAddress as RetValidatorAddress, + CanonicalVaultAddress as RetVaultAddress, +}; + +pub trait AddressViaRet: Sized { + fn new( + node_id: impl Into, + network_id: NetworkID, + ) -> Result; +} + +macro_rules! decl_ret_wrapped_address { + ($addr_name:ty,$ret_addr:ty) => { + paste! { + /// UniFFI conversion for RET types which are DisplayFromStr using String as builtin. + impl crate::UniffiCustomTypeConverter for [] { + 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| { + error!("Failed to UniFFI decode String from FFI via RET, RET error: {:?}", e); + CommonError::FailedToDecodeAddressFromBech32 { bad_value: val } + }) + .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() + } + } + + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + derive_more::FromStr, + derive_more::Display, + SerializeDisplay, + DeserializeFromStr, + )] + pub struct [](pub(crate) $ret_addr); // MUST add [Custom]typedef string Inner to sargon.udl as well + + impl From<$ret_addr> for [] { + fn from(value: $ret_addr) -> Self { + Self(value) + } + } + impl From<[]> for $ret_addr { + fn from(value: []) -> Self { + value.0 + } + } + + impl From<[]> for $addr_name { + fn from(value: []) -> Self { + Self { __inner: value } + } + } + + impl From<$addr_name> for [] { + fn from(value: $addr_name) -> Self { + value.__inner + } + } + + impl $addr_name { + pub fn address(&self) -> String { + self.to_string() + } + + pub fn network_id(&self) -> NetworkID { + self.__inner.0.network_id().try_into().expect("Should have known all network ids") + } + + pub fn entity_type(&self) -> ScryptoEntityType { + self.__inner.0.entity_type() + } + + pub fn try_from_bech32(bech32: impl AsRef) -> Result { + bech32.as_ref().parse::<[]>() + .map_err(|e| { + error!("Failed Bech32 decode String, RET error: {:?}", e); + CommonError::FailedToDecodeAddressFromBech32 { bad_value: bech32.as_ref().to_owned() } + }) + .map(Into::::into) + } + } + + impl AddressViaRet for $addr_name { + fn new( + node_id: impl Into, + network_id: NetworkID, + ) -> Result { + let node_id: ScryptoNodeId = node_id.into(); + $ret_addr::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 } + }) + .map(|r| Into::<[]>::into(r)) + .map(|i| Into::<$addr_name>::into(i)) + } + } + } + }; +} + +decl_ret_wrapped_address!(AccessControllerAddress, RetAccessControllerAddress); +decl_ret_wrapped_address!(AccountAddress, RetAccountAddress); +decl_ret_wrapped_address!(ComponentAddress, RetComponentAddress); +decl_ret_wrapped_address!(IdentityAddress, RetIdentityAddress); +decl_ret_wrapped_address!(PoolAddress, RetPoolAddress); +decl_ret_wrapped_address!(PackageAddress, RetPackageAddress); +decl_ret_wrapped_address!(ResourceAddress, RetResourceAddress); +decl_ret_wrapped_address!(ValidatorAddress, RetValidatorAddress); +decl_ret_wrapped_address!(VaultAddress, RetVaultAddress); diff --git a/src/profile/v100/entity/abstract_entity_type.rs b/src/profile/v100/entity/abstract_entity_type.rs index 9b27cc8c0..6d5766077 100644 --- a/src/profile/v100/entity/abstract_entity_type.rs +++ b/src/profile/v100/entity/abstract_entity_type.rs @@ -19,37 +19,27 @@ use radix_engine_common::types::EntityType as ScryptoEntityType; )] #[repr(u32)] // it is u32 since used in Derivation Paths (CAP26) where each component is a u32. pub enum AbstractEntityType { - /// The entity type used by Accounts. + /// The entity type used by Accounts created by the Radix Wallet. Account, /// The entity type used by Personas. Identity, - /// Resource address - Resource, } -impl AbstractEntityType { - /// Conversion of the Radix Engines type for EntityType to Self. - pub fn try_from(value: ScryptoEntityType) -> Result { - match value { - ScryptoEntityType::GlobalVirtualEd25519Account => Ok(Self::Account), - ScryptoEntityType::GlobalVirtualSecp256k1Account => { - Ok(Self::Account) - } - ScryptoEntityType::GlobalVirtualEd25519Identity => { - Ok(Self::Identity) - } - ScryptoEntityType::GlobalFungibleResourceManager => { - Ok(Self::Resource) - } - _ => Err(CommonError::UnsupportedEntityType), - } - } - /// Human Readable Part (HRP) used to create account and identity addresses. - pub fn hrp(&self) -> String { - match self { - Self::Account => "account".to_string(), - Self::Identity => "identity".to_string(), - Self::Resource => "resource".to_string(), - } - } -} +// impl TryFrom for AbstractEntityType { +// type Error = crate::CommonError; + +// fn try_from(value: ScryptoEntityType) -> Result { +// match value { +// ScryptoEntityType::GlobalVirtualEd25519Account => { +// Ok(Self::Account) +// } +// ScryptoEntityType::GlobalVirtualSecp256k1Account => { +// Ok(Self::Account) +// } +// ScryptoEntityType::GlobalVirtualEd25519Identity => { +// Ok(Self::Identity) +// } +// _ => Err(CommonError::UnsupportedEntityType), +// } +// } +// } diff --git a/src/profile/v100/entity/account/account.rs b/src/profile/v100/entity/account/account.rs index 8804cddfd..15a7dfe00 100644 --- a/src/profile/v100/entity/account/account.rs +++ b/src/profile/v100/entity/account/account.rs @@ -99,6 +99,7 @@ impl Account { } } + impl Identifiable for Account { type ID = AccountAddress; @@ -148,7 +149,7 @@ impl Account { appearance_id: AppearanceID, ) -> Self { Self { - network_id: address.network_id, + network_id: address.network_id(), address, display_name, appearance_id, diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception.rs b/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception.rs index 989993124..df8a067e0 100644 --- a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception.rs +++ b/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/asset_exception.rs @@ -10,8 +10,6 @@ use crate::prelude::*; PartialEq, Eq, Hash, - PartialOrd, - Ord, uniffi::Record, )] #[serde(rename_all = "camelCase")] diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/resource_or_non_fungible.rs b/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/resource_or_non_fungible.rs index 4ef4cad4c..363c5ab0f 100644 --- a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/resource_or_non_fungible.rs +++ b/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/resource_or_non_fungible.rs @@ -9,8 +9,6 @@ use crate::prelude::*; PartialEq, Eq, Hash, - PartialOrd, - Ord, uniffi::Enum, )] #[serde(tag = "discriminator")] diff --git a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits.rs b/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits.rs index 2e4b95e53..b9f9a0706 100644 --- a/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits.rs +++ b/src/profile/v100/entity/account/on_ledger_settings/third_party_deposits/third_party_deposits.rs @@ -124,12 +124,12 @@ mod tests { .unwrap(), DepositAddressExceptionRule::Allow, ); + + let nf: NonFungibleGlobalId = "resource_sim1ngktvyeenvvqetnqwysevcx5fyvl6hqe36y3rkhdfdn6uzvt5366ha:".parse().unwrap(); let model = ThirdPartyDeposits::with_rule_and_lists( DepositRule::AcceptKnown, - BTreeSet::from_iter([excp1, excp2]), - BTreeSet::from_iter( - [ResourceOrNonFungible::NonFungible { value: "resource_sim1ngktvyeenvvqetnqwysevcx5fyvl6hqe36y3rkhdfdn6uzvt5366ha:".parse().unwrap()}], - ), + [excp1, excp2].into_iter(), + [ResourceOrNonFungible::NonFungible { value: nf }].into_iter(), ); assert_eq_after_json_roundtrip( diff --git a/src/profile/v100/networks/network/authorized_dapp/authorized_dapp.rs b/src/profile/v100/networks/network/authorized_dapp/authorized_dapp.rs index f163fd54c..0915a1f62 100644 --- a/src/profile/v100/networks/network/authorized_dapp/authorized_dapp.rs +++ b/src/profile/v100/networks/network/authorized_dapp/authorized_dapp.rs @@ -38,8 +38,8 @@ impl AuthorizedDapp { AuthorizedPersonaSimple, >, ) -> Self { - assert_eq!(dapp_definition_address.network_id, network_id, "Discrepancy, found an DappDefinitionAddress on other network than {network_id}"); - assert!(references_to_authorized_personas.ids().iter().all(|i| i.network_id == network_id), "Discrepancy, found an (Authorized)Persona(Simple) on other network than {network_id}"); + assert_eq!(dapp_definition_address.network_id(), network_id, "Discrepancy, found an DappDefinitionAddress on other network than {network_id}"); + assert!(references_to_authorized_personas.ids().iter().all(|i| i.network_id() == network_id), "Discrepancy, found an (Authorized)Persona(Simple) on other network than {network_id}"); Self { network_id, dapp_definition_address, diff --git a/src/profile/v100/networks/profile_networks.rs b/src/profile/v100/networks/profile_networks.rs index f4f1f6b0f..8ef3ae8d7 100644 --- a/src/profile/v100/networks/profile_networks.rs +++ b/src/profile/v100/networks/profile_networks.rs @@ -25,7 +25,7 @@ impl ProfileNetworks { impl ProfileNetworks { pub fn get_account(&self, address: &AccountAddress) -> Option { - self.get(&address.network_id) + self.get(&address.network_id()) .and_then(|n| n.accounts.get_account_by_address(address)) .cloned() } @@ -39,7 +39,7 @@ impl ProfileNetworks { where F: FnMut(&mut Account), { - self.update_with(&address.network_id, |n| { + self.update_with(&address.network_id(), |n| { _ = n.update_account(address, |a| mutate(a)) }); self.get_account(address) diff --git a/src/sargon.udl b/src/sargon.udl index af9c0724f..a4b1d43b0 100644 --- a/src/sargon.udl +++ b/src/sargon.udl @@ -14,6 +14,30 @@ typedef sequence BagOfBytes; typedef string InnerDecimal; [Custom] -typedef string RetAccountAddress; +typedef string InnerAccountAddress; + +[Custom] +typedef string InnerIdentityAddress; + +[Custom] +typedef string InnerResourceAddress; + +[Custom] +typedef string InnerVaultAddress; + +[Custom] +typedef string InnerPoolAddress; + +[Custom] +typedef string InnerAccessControllerAddress; + +[Custom] +typedef string InnerValidatorAddress; + +[Custom] +typedef string InnerPackageAddress; + +[Custom] +typedef string InnerComponentAddress; namespace sargon {}; \ No newline at end of file diff --git a/src/wallet/wallet_accounts.rs b/src/wallet/wallet_accounts.rs index c4ea5d1a4..8f32518af 100644 --- a/src/wallet/wallet_accounts.rs +++ b/src/wallet/wallet_accounts.rs @@ -458,7 +458,7 @@ mod tests { }, |a, q| { assert_eq!( - a.address.address, + a.address.address(), "account_rdx12yy8n09a0w907vrjyj4hws2yptrm3rdjv84l9sr24e3w7pk7nuxst8" ); assert_eq!(a.appearance_id, AppearanceID::new(0).unwrap()); // using `0` since first. @@ -496,7 +496,7 @@ mod tests { }, |a, q| { assert_eq!( - a.address.address, + a.address.address(), "account_rdx12xvg2sssh0rpca6e8xyqv5vf4nqu928083yzf0fdrnvjdz2pvc000x" // pretty cool address! Random! ); assert_eq!(a.appearance_id, AppearanceID::new(2).unwrap()); diff --git a/src/wrapped_radix_engine_toolkit/dummy_types.rs b/src/wrapped_radix_engine_toolkit/dummy_types.rs index 04df5f6b4..24034ec0e 100644 --- a/src/wrapped_radix_engine_toolkit/dummy_types.rs +++ b/src/wrapped_radix_engine_toolkit/dummy_types.rs @@ -17,12 +17,5 @@ macro_rules! dummy_sargon { }; } -dummy_sargon!(PackageAddress); -dummy_sargon!(ComponentAddress); -dummy_sargon!(AccessControllerAddress); -dummy_sargon!(VaultAddress); -dummy_sargon!(ValidatorAddress); -dummy_sargon!(ResourcePoolAddress); - // Rename and use Decimal type! dummy_sargon!(RETDecimal);