radix_clis/utils/
resource_specifier.rs

1//! This module implements a number of useful utility functions used to prepare instructions for
2//! calling methods and functions when a known schema is provided. This module implements all of the
3//! parsing logic as well as the logic needed ot add these instructions to the original manifest
4//! builder that is being used.
5
6use radix_common::math::ParseDecimalError;
7use radix_common::prelude::*;
8
9#[derive(Debug)]
10pub enum ParseResourceSpecifierError {
11    InvalidAmount(ParseDecimalError),
12    InvalidResourceAddress(String),
13    InvalidNonFungibleLocalId(ParseNonFungibleLocalIdError),
14    MoreThanOneAmountSpecified,
15}
16
17#[derive(Debug, PartialEq, Eq)]
18pub enum ResourceSpecifier {
19    Amount(Decimal, ResourceAddress),
20    Ids(IndexSet<NonFungibleLocalId>, ResourceAddress),
21}
22
23impl FromStr for ResourceSpecifier {
24    type Err = ParseResourceSpecifierError;
25
26    fn from_str(s: &str) -> Result<Self, Self::Err> {
27        parse_resource_specifier(s, &AddressBech32Decoder::for_simulator())
28    }
29}
30
31/// Attempts to parse a string as a [`ResourceSpecifier`] object.
32///
33/// Given a string, this function attempts to parse that string as a fungible or non-fungible
34/// [`ResourceSpecifier`]. When a resource address is encountered in the string, the passed bech32m
35/// decoder is used to attempt to decode the address.
36///
37/// The format expected for the string representation of fungible and non-fungible resource
38/// specifiers differs. The following elaborates on the formats and the parsing modes.
39///
40/// ## Fungible Resource Specifiers
41///
42/// The string representation of fungible resource addresses is that it is a [`Decimal`] amount
43/// followed by a resource address for that given resource. Separating the amount and the resource
44/// address is a comma. The following is what that looks like as well as an example of that
45///
46/// ```txt
47/// <resource_address>:<amount>
48/// ```
49///
50/// As an example, say that `resource_sim1qqw9095s39kq2vxnzymaecvtpywpkughkcltw4pzd4pse7dvr0` is a
51/// fungible resource which we wish to create a resource specifier of `12.91` of, then the string
52/// format to use for the fungible specifier would be:
53///
54/// ```txt
55/// resource_sim1qqw9095s39kq2vxnzymaecvtpywpkughkcltw4pzd4pse7dvr0:12.91
56/// ```
57///
58/// ## Non-Fungible Resource Specifiers
59///
60/// The string representation of non-fungible resource specifiers follows the same format which will
61/// be used for the wallet, explorer, and other parts of our system. A string non-fungible resource
62/// specifier beings with a Bech32m encoded resource address, then a colon, and then a list of comma
63/// separated non-fungible ids that we wish to specify.
64///
65/// The type of these non-fungible ids does not need to be provided in the non-fungible resource
66/// specifier string representation; this is because the type is automatically looked up for the
67/// given resource address and then used as context for the parsing of the given non-fungible id
68/// string.
69///
70/// The format of the string representation of non-fungible resource specifiers is:
71///
72/// ```txt
73/// <resource_address>:<non_fungible_local_id_1>,<non_fungible_local_id_2>,...,<non_fungible_local_id_n>
74/// ```
75///
76/// As an example, say that `resource_sim1qqw9095s39kq2vxnzymaecvtpywpkughkcltw4pzd4pse7dvr0` is a
77/// non-fungible resource which has a non-fungible id type of [`NonFungibleIdType::Integer`], say that
78/// we wish to specify non-fungible tokens of this resource with the ids: 12, 900, 181, the string
79/// representation of the non-fungible resource specifier would be:
80///
81/// ```txt
82/// resource_sim1qqw9095s39kq2vxnzymaecvtpywpkughkcltw4pzd4pse7dvr0:#12#,#900#,#181#
83/// ```
84///
85/// As you can see from the example above, there was no need to specify the non-fungible id type in
86/// the resource specifier string, as mentioned above, this is because this information can be
87/// looked up from the simulator's substate store.
88pub fn parse_resource_specifier(
89    input: &str,
90    address_bech32_decoder: &AddressBech32Decoder,
91) -> Result<ResourceSpecifier, ParseResourceSpecifierError> {
92    // Splitting up the input into two parts: the resource address and the non-fungible ids
93    let tokens = input
94        .trim()
95        .split(':')
96        .map(|s| s.trim())
97        .collect::<Vec<_>>();
98
99    // There MUST only be two tokens in the tokens vector, one for the resource address, and
100    // another for the non-fungible ids. If there is more or less, then this function returns
101    // an error.
102    if tokens.len() != 2 {
103        return Err(ParseResourceSpecifierError::MoreThanOneAmountSpecified);
104    }
105
106    // If the second part starts with a non-fungible local id prefix, we consider it as non-fungible resource specifier.
107    let is_non_fungible = tokens[1].starts_with("<")
108        || tokens[1].starts_with("#")
109        || tokens[1].starts_with("[")
110        || tokens[1].starts_with("{");
111
112    if !is_non_fungible {
113        let resource_address_string = tokens[0];
114        let amount_string = tokens[1];
115
116        let amount = amount_string
117            .parse()
118            .map_err(ParseResourceSpecifierError::InvalidAmount)?;
119        let resource_address =
120            ResourceAddress::try_from_bech32(address_bech32_decoder, resource_address_string)
121                .ok_or(ParseResourceSpecifierError::InvalidResourceAddress(
122                    resource_address_string.to_string(),
123                ))?;
124
125        Ok(ResourceSpecifier::Amount(amount, resource_address))
126    } else {
127        // Paring the resource address fully first to use it for the non-fungible id type ledger
128        // lookup
129        let resource_address_string = tokens[0];
130        let resource_address =
131            ResourceAddress::try_from_bech32(address_bech32_decoder, resource_address_string)
132                .ok_or(ParseResourceSpecifierError::InvalidResourceAddress(
133                    resource_address_string.to_string(),
134                ))?;
135
136        // Parsing the non-fungible ids with the available id type
137        let non_fungible_local_ids = tokens[1]
138            .split(',')
139            .map(|s| NonFungibleLocalId::from_str(s.trim()))
140            .collect::<Result<IndexSet<_>, _>>()
141            .map_err(ParseResourceSpecifierError::InvalidNonFungibleLocalId)?;
142
143        Ok(ResourceSpecifier::Ids(
144            non_fungible_local_ids,
145            resource_address,
146        ))
147    }
148}
149
150#[cfg(test)]
151mod test {
152    use super::*;
153
154    #[test]
155    pub fn parsing_of_fungible_resource_specifier_succeeds() {
156        // Arrange
157        let resource_specifier_string =
158            "resource_sim1thvwu8dh6lk4y9mntemkvj25wllq8adq42skzufp4m8wxxuemugnez:900";
159        let address_bech32_decoder = AddressBech32Decoder::for_simulator();
160
161        // Act
162        let resource_specifier =
163            parse_resource_specifier(resource_specifier_string, &address_bech32_decoder)
164                .expect("Failed to parse resource specifier");
165
166        // Assert
167        assert_eq!(
168            resource_specifier,
169            ResourceSpecifier::Amount(
170                900.into(),
171                ResourceAddress::try_from_bech32(
172                    &address_bech32_decoder,
173                    "resource_sim1thvwu8dh6lk4y9mntemkvj25wllq8adq42skzufp4m8wxxuemugnez"
174                )
175                .unwrap()
176            )
177        )
178    }
179
180    #[test]
181    pub fn parsing_of_single_non_fungible_resource_specifier_succeeds() {
182        // Arrange
183        let resource_specifier_string =
184            "resource_sim1nfxxxxxxxxxxsecpsgxxxxxxxxx004638826440xxxxxxxxxwj8qq5:[1f5db2614c1c4c626e9c279349b240af7cb939ead29058fdff2c]";
185        let address_bech32_decoder = AddressBech32Decoder::for_simulator();
186
187        // Act
188        let resource_specifier =
189            parse_resource_specifier(resource_specifier_string, &address_bech32_decoder)
190                .expect("Failed to parse resource specifier");
191
192        // Assert
193        assert_eq!(
194            resource_specifier,
195            ResourceSpecifier::Ids(
196                IndexSet::from([NonFungibleLocalId::bytes(vec![
197                    31, 93, 178, 97, 76, 28, 76, 98, 110, 156, 39, 147, 73, 178, 64, 175, 124, 185,
198                    57, 234, 210, 144, 88, 253, 255, 44
199                ])
200                .unwrap()]),
201                SECP256K1_SIGNATURE_RESOURCE
202            )
203        )
204    }
205
206    #[test]
207
208    pub fn parsing_of_multiple_non_fungible_resource_specifier_succeeds() {
209        // Arrange
210        let resource_specifier_string =
211            "resource_sim1nfxxxxxxxxxxsecpsgxxxxxxxxx004638826440xxxxxxxxxwj8qq5:[1f5db2614c1c4c626e9c279349b240af7cb939ead29058fdff2c],[d85dc446d8e5eff48db25b56f6b5001d14627b5a199598485a8d],[005d1ae87b0e7c5401d38e58d43291ffbd9ba6e1da54f87504a7]";
212        let address_bech32_decoder = AddressBech32Decoder::for_simulator();
213
214        // Act
215        let resource_specifier =
216            parse_resource_specifier(resource_specifier_string, &address_bech32_decoder)
217                .expect("Failed to parse resource specifier");
218
219        // Assert
220        assert_eq!(
221            resource_specifier,
222            ResourceSpecifier::Ids(
223                IndexSet::from([
224                    NonFungibleLocalId::bytes(vec![
225                        31, 93, 178, 97, 76, 28, 76, 98, 110, 156, 39, 147, 73, 178, 64, 175, 124,
226                        185, 57, 234, 210, 144, 88, 253, 255, 44
227                    ])
228                    .unwrap(),
229                    NonFungibleLocalId::bytes(vec![
230                        216, 93, 196, 70, 216, 229, 239, 244, 141, 178, 91, 86, 246, 181, 0, 29,
231                        20, 98, 123, 90, 25, 149, 152, 72, 90, 141
232                    ])
233                    .unwrap(),
234                    NonFungibleLocalId::bytes(vec![
235                        0, 93, 26, 232, 123, 14, 124, 84, 1, 211, 142, 88, 212, 50, 145, 255, 189,
236                        155, 166, 225, 218, 84, 248, 117, 4, 167
237                    ])
238                    .unwrap()
239                ]),
240                SECP256K1_SIGNATURE_RESOURCE
241            )
242        )
243    }
244}