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}