scrypto_test/sdk/resource/
bucket_factory.rs

1use crate::prelude::*;
2
3/// A factory for buckets that can create them (for testing) through multiple creation strategies
4pub struct BucketFactory;
5
6impl BucketFactory {
7    pub fn create_fungible_bucket<S>(
8        resource_address: ResourceAddress,
9        amount: Decimal,
10        creation_strategy: CreationStrategy,
11        env: &mut TestEnvironment<S>,
12    ) -> Result<FungibleBucket, RuntimeError>
13    where
14        S: SubstateDatabase + CommittableSubstateDatabase + 'static,
15    {
16        let bucket = Self::create_bucket(
17            FactoryResourceSpecifier::Amount(resource_address, amount),
18            creation_strategy,
19            env,
20        )?;
21        Ok(FungibleBucket(bucket))
22    }
23
24    pub fn create_non_fungible_bucket<I, D, S>(
25        resource_address: ResourceAddress,
26        non_fungibles: I,
27        creation_strategy: CreationStrategy,
28        env: &mut TestEnvironment<S>,
29    ) -> Result<NonFungibleBucket, RuntimeError>
30    where
31        I: IntoIterator<Item = (NonFungibleLocalId, D)>,
32        D: ScryptoEncode,
33        S: SubstateDatabase + CommittableSubstateDatabase + 'static,
34    {
35        let bucket = Self::create_bucket(
36            FactoryResourceSpecifier::Ids(
37                resource_address,
38                non_fungibles
39                    .into_iter()
40                    .map(|(id, data)| {
41                        (
42                            id,
43                            scrypto_decode::<ScryptoValue>(&scrypto_encode(&data).unwrap())
44                                .unwrap(),
45                        )
46                    })
47                    .collect(),
48            ),
49            creation_strategy,
50            env,
51        )?;
52        Ok(NonFungibleBucket(bucket))
53    }
54
55    pub fn create_bucket<S>(
56        resource_specifier: FactoryResourceSpecifier,
57        creation_strategy: CreationStrategy,
58        env: &mut TestEnvironment<S>,
59    ) -> Result<Bucket, RuntimeError>
60    where
61        S: SubstateDatabase + CommittableSubstateDatabase + 'static,
62    {
63        match (&resource_specifier, creation_strategy) {
64            (
65                FactoryResourceSpecifier::Amount(resource_address, amount),
66                CreationStrategy::DisableAuthAndMint,
67            ) => env.with_auth_module_disabled(|env| {
68                let bucket = ResourceManager(*resource_address).mint_fungible(*amount, env)?;
69                Ok(bucket.into())
70            }),
71            (
72                FactoryResourceSpecifier::Ids(resource_address, ids),
73                CreationStrategy::DisableAuthAndMint,
74            ) => env.with_auth_module_disabled(|env| {
75                let bucket = ResourceManager(*resource_address).mint_non_fungible(ids.clone(), env)?;
76                Ok(bucket.into())
77            }),
78            (
79                FactoryResourceSpecifier::Amount(resource_address, amount),
80                CreationStrategy::Mock,
81            ) => env.with_auth_module_disabled(|env| {
82                assert!(Self::validate_resource_specifier(&resource_specifier, env)?);
83
84                env.as_method_actor(
85                    resource_address.into_node_id(),
86                    ModuleId::Main,
87                    FUNGIBLE_RESOURCE_MANAGER_MINT_IDENT,
88                    |env| {
89                        env.new_simple_object(
90                            FUNGIBLE_BUCKET_BLUEPRINT,
91                            indexmap!(
92                                FungibleBucketField::Liquid.into() => FieldValue::new(LiquidFungibleResource::new(*amount)),
93                                FungibleBucketField::Locked.into() => FieldValue::new(LockedFungibleResource::default()),
94                            )
95                        ).map(|node_id| Bucket(Own(node_id)))
96                    },
97                )?
98            }),
99            (
100                FactoryResourceSpecifier::Ids(resource_address, non_fungibles),
101                CreationStrategy::Mock,
102            ) => env.with_auth_module_disabled(|env| {
103                assert!(Self::validate_resource_specifier(&resource_specifier, env)?);
104
105                env.as_method_actor(
106                    resource_address.into_node_id(),
107                    ModuleId::Main,
108                    NON_FUNGIBLE_RESOURCE_MANAGER_MINT_IDENT,
109                    |env| {
110                        for (local_id, data) in non_fungibles.iter() {
111                            let non_fungible_handle = env.actor_open_key_value_entry(
112                                ACTOR_STATE_SELF,
113                                NonFungibleResourceManagerCollection::DataKeyValue.collection_index(),
114                                &local_id.to_key(),
115                                LockFlags::MUTABLE,
116                            )?;
117
118                            let cur_non_fungible = env
119                                .key_value_entry_get_typed::<NonFungibleResourceManagerDataEntryPayload>(
120                                    non_fungible_handle,
121                                )?;
122
123                            if cur_non_fungible.is_some() {
124                                return Err(RuntimeError::ApplicationError(
125                                    ApplicationError::NonFungibleResourceManagerError(
126                                        NonFungibleResourceManagerError::NonFungibleAlreadyExists(Box::new(
127                                            NonFungibleGlobalId::new(*resource_address, local_id.clone()),
128                                        )),
129                                    ),
130                                ));
131                            }
132
133                            env.key_value_entry_set_typed(
134                                non_fungible_handle,
135                                NonFungibleResourceManagerDataEntryPayload::from_content_source(data.clone()),
136                            )?;
137                            env.key_value_entry_close(non_fungible_handle)?;
138                        }
139
140                        env.new_simple_object(
141                            NON_FUNGIBLE_BUCKET_BLUEPRINT,
142                            indexmap!(
143                                NonFungibleBucketField::Liquid.into() => FieldValue::new(LiquidNonFungibleResource::new(non_fungibles.keys().cloned().collect())),
144                                NonFungibleBucketField::Locked.into() => FieldValue::new(LockedNonFungibleResource::default()),
145                            )
146                        ).map(|node_id| Bucket(Own(node_id)))
147                    },
148                )?
149            }),
150        }
151    }
152
153    fn validate_resource_specifier<S>(
154        resource_specifier: &FactoryResourceSpecifier,
155        env: &mut TestEnvironment<S>,
156    ) -> Result<bool, RuntimeError>
157    where
158        S: SubstateDatabase + CommittableSubstateDatabase + 'static,
159    {
160        // Validating the resource is correct - can't mint IDs of a fungible resource and can't mint
161        // an amount of a non-fungible resource.
162        match resource_specifier {
163            FactoryResourceSpecifier::Amount(resource_address, ..)
164                if resource_address.is_fungible() =>
165            {
166                // No additional validations are needed for fungible resources
167            }
168            FactoryResourceSpecifier::Ids(resource_address, non_fungibles)
169                if !resource_address.is_fungible() =>
170            {
171                // Some more validations are needed for non-fungibles.
172
173                // Validate that the ids provided are:
174                // 1. All of one type.
175                // 2. This one type is the type of the non-fungible local ids.
176                let id_type = {
177                    let mut iter = non_fungibles
178                        .keys()
179                        .map(|id| id.id_type())
180                        .collect::<IndexSet<NonFungibleIdType>>()
181                        .into_iter();
182                    let Some(id_type) = iter.next() else {
183                        return Ok(true);
184                    };
185                    if iter.next().is_some() {
186                        return Ok(false);
187                    }
188                    id_type
189                };
190
191                let ResourceType::NonFungible {
192                    id_type: expected_id_type,
193                } = ResourceManager(*resource_address).resource_type(env)?
194                else {
195                    return Ok(false);
196                };
197
198                if id_type != expected_id_type {
199                    return Ok(false);
200                }
201            }
202            _ => return Ok(false),
203        }
204        Ok(true)
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn verify_validate_resource_specifier_method() {
214        // Arrange
215        let mut env = TestEnvironment::new();
216        let bucket = ResourceBuilder::new_integer_non_fungible::<()>(OwnerRole::None)
217            .mint_initial_supply([], &mut env)
218            .unwrap();
219
220        let items = indexmap!(
221            NonFungibleLocalId::integer(1u64) => SborValue::U8 { value: 1 },
222            NonFungibleLocalId::integer(2u64) => SborValue::U8 { value: 2 },
223        );
224
225        let resource =
226            FactoryResourceSpecifier::Ids(bucket.resource_address(&mut env).unwrap(), items);
227
228        // Act
229        let result = BucketFactory::validate_resource_specifier(&resource, &mut env).unwrap();
230
231        // Assert
232        assert!(result);
233    }
234
235    #[test]
236    fn verify_validate_resource_specifier_method_should_fail() {
237        // Arrange
238        let mut env = TestEnvironment::new();
239        let bucket = ResourceBuilder::new_integer_non_fungible::<()>(OwnerRole::None)
240            .mint_initial_supply([], &mut env)
241            .unwrap();
242
243        let items = indexmap!(
244            NonFungibleLocalId::integer(1u64) => SborValue::U8 { value: 1 },
245            NonFungibleLocalId::string("value").unwrap() => SborValue::U8 { value: 2 },
246        );
247
248        let resource =
249            FactoryResourceSpecifier::Ids(bucket.resource_address(&mut env).unwrap(), items);
250
251        // Act
252        let result = BucketFactory::validate_resource_specifier(&resource, &mut env).unwrap();
253
254        // Assert
255        assert!(!result);
256    }
257}