radix_engine/object_modules/metadata/
package.rs

1use crate::internal_prelude::*;
2use crate::{errors::*, event_schema, roles_template};
3use radix_blueprint_schema_init::{
4    BlueprintFunctionsSchemaInit, BlueprintSchemaInit, FunctionSchemaInit, TypeRef,
5};
6use radix_engine_interface::api::field_api::LockFlags;
7use radix_engine_interface::api::{FieldValue, GenericArgs, KVEntry, SystemApi, ACTOR_STATE_SELF};
8use radix_engine_interface::blueprints::package::{
9    AuthConfig, BlueprintDefinitionInit, BlueprintType, FunctionAuth, MethodAuthTemplate,
10    PackageDefinition,
11};
12use radix_engine_interface::object_modules::metadata::*;
13use radix_native_sdk::runtime::Runtime;
14
15use super::{RemoveMetadataEvent, SetMetadataEvent};
16
17#[derive(Debug, Clone, Eq, PartialEq, ScryptoSbor)]
18pub enum MetadataError {
19    KeyStringExceedsMaxLength { max: usize, actual: usize },
20    ValueSborExceedsMaxLength { max: usize, actual: usize },
21    ValueDecodeError(DecodeError),
22    MetadataValueValidationError(MetadataValueValidationError),
23    MetadataKeyValidationError(MetadataKeyValidationError),
24}
25
26declare_native_blueprint_state! {
27    blueprint_ident: Metadata,
28    blueprint_snake_case: metadata,
29    features: {
30    },
31    fields: {
32    },
33    collections: {
34        entries: KeyValue {
35            entry_ident: Entry,
36            key_type: {
37                kind: Static,
38                content_type: String,
39            },
40            value_type: {
41                kind: StaticSingleVersioned,
42            },
43            allow_ownership: false,
44        },
45    }
46}
47
48pub type MetadataEntryV1 = MetadataValue;
49
50pub struct MetadataNativePackage;
51
52impl MetadataNativePackage {
53    pub fn definition() -> PackageDefinition {
54        let mut aggregator = TypeAggregator::<ScryptoCustomTypeKind>::new();
55
56        let state = MetadataStateSchemaInit::create_schema_init(&mut aggregator);
57
58        let mut functions = index_map_new();
59        functions.insert(
60            METADATA_CREATE_IDENT.to_string(),
61            FunctionSchemaInit {
62                receiver: None,
63                input: TypeRef::Static(
64                    aggregator.add_child_type_and_descendents::<MetadataCreateInput>(),
65                ),
66                output: TypeRef::Static(
67                    aggregator.add_child_type_and_descendents::<MetadataCreateOutput>(),
68                ),
69                export: METADATA_CREATE_IDENT.to_string(),
70            },
71        );
72        functions.insert(
73            METADATA_CREATE_WITH_DATA_IDENT.to_string(),
74            FunctionSchemaInit {
75                receiver: None,
76                input: TypeRef::Static(
77                    aggregator.add_child_type_and_descendents::<MetadataCreateWithDataInput>(),
78                ),
79                output: TypeRef::Static(
80                    aggregator.add_child_type_and_descendents::<MetadataCreateWithDataOutput>(),
81                ),
82                export: METADATA_CREATE_WITH_DATA_IDENT.to_string(),
83            },
84        );
85        functions.insert(
86            METADATA_SET_IDENT.to_string(),
87            FunctionSchemaInit {
88                receiver: Some(ReceiverInfo::normal_ref_mut()),
89                input: TypeRef::Static(
90                    aggregator.add_child_type_and_descendents::<MetadataSetInput>(),
91                ),
92                output: TypeRef::Static(
93                    aggregator.add_child_type_and_descendents::<MetadataSetOutput>(),
94                ),
95                export: METADATA_SET_IDENT.to_string(),
96            },
97        );
98        functions.insert(
99            METADATA_LOCK_IDENT.to_string(),
100            FunctionSchemaInit {
101                receiver: Some(ReceiverInfo::normal_ref_mut()),
102                input: TypeRef::Static(
103                    aggregator.add_child_type_and_descendents::<MetadataLockInput>(),
104                ),
105                output: TypeRef::Static(
106                    aggregator.add_child_type_and_descendents::<MetadataLockOutput>(),
107                ),
108                export: METADATA_LOCK_IDENT.to_string(),
109            },
110        );
111        functions.insert(
112            METADATA_GET_IDENT.to_string(),
113            FunctionSchemaInit {
114                receiver: Some(ReceiverInfo::normal_ref()),
115                input: TypeRef::Static(
116                    aggregator.add_child_type_and_descendents::<MetadataGetInput>(),
117                ),
118                output: TypeRef::Static(
119                    aggregator.add_child_type_and_descendents::<MetadataGetOutput>(),
120                ),
121                export: METADATA_GET_IDENT.to_string(),
122            },
123        );
124        functions.insert(
125            METADATA_REMOVE_IDENT.to_string(),
126            FunctionSchemaInit {
127                receiver: Some(ReceiverInfo::normal_ref_mut()),
128                input: TypeRef::Static(
129                    aggregator.add_child_type_and_descendents::<MetadataRemoveInput>(),
130                ),
131                output: TypeRef::Static(
132                    aggregator.add_child_type_and_descendents::<MetadataRemoveOutput>(),
133                ),
134                export: METADATA_REMOVE_IDENT.to_string(),
135            },
136        );
137
138        let events = event_schema! {
139            aggregator,
140            [SetMetadataEvent, RemoveMetadataEvent]
141        };
142
143        let schema = generate_full_schema(aggregator);
144        let blueprints = indexmap!(
145            METADATA_BLUEPRINT.to_string() => BlueprintDefinitionInit {
146                blueprint_type: BlueprintType::default(),
147                is_transient: true,
148                feature_set: indexset!(),
149                dependencies: indexset!(),
150
151                schema: BlueprintSchemaInit {
152                    generics: vec![],
153                    schema,
154                    state,
155                    events,
156                    types: BlueprintTypeSchemaInit::default(),
157                    functions: BlueprintFunctionsSchemaInit {
158                        functions,
159                    },
160                    hooks: BlueprintHooksInit::default(),
161                },
162
163                royalty_config: PackageRoyaltyConfig::default(),
164                auth_config: AuthConfig {
165                    function_auth: FunctionAuth::AllowAll,
166                    method_auth: MethodAuthTemplate::StaticRoleDefinition(
167                        roles_template!(
168                            roles {
169                                METADATA_SETTER_ROLE => updaters: [METADATA_SETTER_UPDATER_ROLE];
170                                METADATA_SETTER_UPDATER_ROLE => updaters: [METADATA_SETTER_UPDATER_ROLE];
171                                METADATA_LOCKER_ROLE => updaters: [METADATA_LOCKER_UPDATER_ROLE];
172                                METADATA_LOCKER_UPDATER_ROLE => updaters: [METADATA_LOCKER_UPDATER_ROLE];
173                            },
174                            methods {
175                                METADATA_SET_IDENT => [METADATA_SETTER_ROLE];
176                                METADATA_REMOVE_IDENT => [METADATA_SETTER_ROLE];
177                                METADATA_LOCK_IDENT => [METADATA_LOCKER_ROLE];
178                                METADATA_GET_IDENT => MethodAccessibility::Public;
179                            }
180                        ),
181                    ),
182                },
183            }
184        );
185
186        PackageDefinition { blueprints }
187    }
188
189    pub fn invoke_export<Y: SystemApi<RuntimeError>>(
190        export_name: &str,
191        input: &IndexedScryptoValue,
192        api: &mut Y,
193    ) -> Result<IndexedScryptoValue, RuntimeError> {
194        match export_name {
195            METADATA_CREATE_IDENT => {
196                let _input: MetadataCreateInput = input.as_typed().map_err(|e| {
197                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
198                })?;
199
200                let rtn = Self::create(api)?;
201
202                Ok(IndexedScryptoValue::from_typed(&rtn))
203            }
204            METADATA_CREATE_WITH_DATA_IDENT => {
205                let input: MetadataCreateWithDataInput = input.as_typed().map_err(|e| {
206                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
207                })?;
208
209                let rtn = Self::create_with_data(input.data, api)?;
210
211                Ok(IndexedScryptoValue::from_typed(&rtn))
212            }
213            METADATA_SET_IDENT => {
214                let input: MetadataSetInput = input.as_typed().map_err(|e| {
215                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
216                })?;
217
218                let rtn = Self::set(input.key, input.value, api)?;
219
220                Ok(IndexedScryptoValue::from_typed(&rtn))
221            }
222            METADATA_LOCK_IDENT => {
223                let input: MetadataLockInput = input.as_typed().map_err(|e| {
224                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
225                })?;
226
227                let rtn = Self::lock(input.key, api)?;
228
229                Ok(IndexedScryptoValue::from_typed(&rtn))
230            }
231            METADATA_GET_IDENT => {
232                let input: MetadataGetInput = input.as_typed().map_err(|e| {
233                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
234                })?;
235
236                let rtn = Self::get(input.key, api)?;
237
238                Ok(IndexedScryptoValue::from_typed(&rtn))
239            }
240            METADATA_REMOVE_IDENT => {
241                let input: MetadataRemoveInput = input.as_typed().map_err(|e| {
242                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
243                })?;
244
245                let rtn = Self::remove(input.key, api)?;
246                Ok(IndexedScryptoValue::from_typed(&rtn))
247            }
248            _ => Err(RuntimeError::ApplicationError(
249                ApplicationError::ExportDoesNotExist(export_name.to_string()),
250            )),
251        }
252    }
253
254    pub(crate) fn create<Y: SystemApi<RuntimeError>>(api: &mut Y) -> Result<Own, RuntimeError> {
255        let node_id = api.new_object(
256            METADATA_BLUEPRINT,
257            vec![],
258            GenericArgs::default(),
259            indexmap!(),
260            indexmap!(),
261        )?;
262
263        Ok(Own(node_id))
264    }
265
266    /// This method assumes that the data has been pre-checked.
267    #[allow(clippy::type_complexity)]
268    pub(crate) fn init_system_struct(
269        data: IndexMap<Vec<u8>, (Option<Vec<u8>>, bool)>,
270    ) -> Result<
271        (
272            IndexMap<u8, FieldValue>,
273            IndexMap<u8, IndexMap<Vec<u8>, KVEntry>>,
274        ),
275        MetadataError,
276    > {
277        let mut init_kv_entries = index_map_new();
278        for (key, entry) in data {
279            let kv_entry = KVEntry {
280                value: entry.0,
281                locked: entry.1,
282            };
283
284            init_kv_entries.insert(key, kv_entry);
285        }
286
287        Ok((
288            indexmap!(),
289            indexmap!(MetadataCollection::EntryKeyValue.collection_index() => init_kv_entries),
290        ))
291    }
292
293    pub(crate) fn create_with_data<Y: SystemApi<RuntimeError>>(
294        metadata_init: MetadataInit,
295        api: &mut Y,
296    ) -> Result<Own, RuntimeError> {
297        let metadata_validated = validate_metadata_init(metadata_init)?;
298        let (fields, kv_entries) = Self::init_system_struct(metadata_validated)
299            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::MetadataError(e)))?;
300
301        let node_id = api.new_object(
302            METADATA_BLUEPRINT,
303            vec![],
304            GenericArgs::default(),
305            fields,
306            kv_entries,
307        )?;
308
309        Ok(Own(node_id))
310    }
311
312    pub(crate) fn set<Y: SystemApi<RuntimeError>>(
313        key: String,
314        value: MetadataValue,
315        api: &mut Y,
316    ) -> Result<(), RuntimeError> {
317        let key_sbor = validate_metadata_key(&key).map_err(|e| {
318            RuntimeError::ApplicationError(ApplicationError::MetadataError(
319                MetadataError::MetadataKeyValidationError(e),
320            ))
321        })?;
322
323        let value_sbor = validate_metadata_value(&value).map_err(|e| {
324            RuntimeError::ApplicationError(ApplicationError::MetadataError(
325                MetadataError::MetadataValueValidationError(e),
326            ))
327        })?;
328
329        let handle = api.actor_open_key_value_entry(
330            ACTOR_STATE_SELF,
331            MetadataCollection::EntryKeyValue.collection_index(),
332            &key_sbor,
333            LockFlags::MUTABLE,
334        )?;
335        api.key_value_entry_set(handle, value_sbor)?;
336        api.key_value_entry_close(handle)?;
337
338        Runtime::emit_event(api, SetMetadataEvent { key, value })?;
339
340        Ok(())
341    }
342
343    pub(crate) fn lock<Y: SystemApi<RuntimeError>>(
344        key: String,
345        api: &mut Y,
346    ) -> Result<(), RuntimeError> {
347        let handle = api.actor_open_key_value_entry(
348            ACTOR_STATE_SELF,
349            MetadataCollection::EntryKeyValue.collection_index(),
350            &scrypto_encode(&key).unwrap(),
351            LockFlags::MUTABLE,
352        )?;
353        api.key_value_entry_lock(handle)?;
354        api.key_value_entry_close(handle)?;
355
356        Ok(())
357    }
358
359    pub(crate) fn get<Y: SystemApi<RuntimeError>>(
360        key: String,
361        api: &mut Y,
362    ) -> Result<Option<MetadataValue>, RuntimeError> {
363        let handle = api.actor_open_key_value_entry(
364            ACTOR_STATE_SELF,
365            MetadataCollection::EntryKeyValue.collection_index(),
366            &scrypto_encode(&key).unwrap(),
367            LockFlags::read_only(),
368        )?;
369
370        let data = api.key_value_entry_get(handle)?;
371        let substate: Option<MetadataEntryEntryPayload> = scrypto_decode(&data).unwrap();
372
373        Ok(substate.map(|v: MetadataEntryEntryPayload| v.fully_update_and_into_latest_version()))
374    }
375
376    pub(crate) fn remove<Y: SystemApi<RuntimeError>>(
377        key: String,
378        api: &mut Y,
379    ) -> Result<bool, RuntimeError> {
380        let cur_value: Option<MetadataEntryEntryPayload> = api.actor_remove_key_value_entry_typed(
381            ACTOR_STATE_SELF,
382            0u8,
383            &scrypto_encode(&key).unwrap(),
384        )?;
385        let rtn = cur_value.is_some();
386
387        Runtime::emit_event(api, RemoveMetadataEvent { key })?;
388
389        Ok(rtn)
390    }
391}
392
393#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
394pub enum MetadataKeyValidationError {
395    InvalidValue,
396    InvalidLength { max: usize, actual: usize },
397}
398
399#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
400pub enum MetadataValueValidationError {
401    InvalidURL(String),
402    InvalidOrigin(String),
403    InvalidValue,
404    InvalidLength { max: usize, actual: usize },
405}
406
407#[allow(clippy::type_complexity)]
408pub fn validate_metadata_init(
409    metadata_init: MetadataInit,
410) -> Result<IndexMap<Vec<u8>, (Option<Vec<u8>>, bool)>, RuntimeError> {
411    let mut checked_data = index_map_new();
412    for (key, value) in &metadata_init.data {
413        let key_sbor = validate_metadata_key(key).map_err(|e| {
414            RuntimeError::ApplicationError(ApplicationError::MetadataError(
415                MetadataError::MetadataKeyValidationError(e),
416            ))
417        })?;
418
419        if let Some(v) = &value.value {
420            let value_sbor = validate_metadata_value(v).map_err(|e| {
421                RuntimeError::ApplicationError(ApplicationError::MetadataError(
422                    MetadataError::MetadataValueValidationError(e),
423                ))
424            })?;
425            checked_data.insert(key_sbor, (Some(value_sbor), value.lock));
426        } else {
427            checked_data.insert(key_sbor, (None, value.lock));
428        }
429    }
430    Ok(checked_data)
431}
432
433pub fn validate_metadata_key(key: &str) -> Result<Vec<u8>, MetadataKeyValidationError> {
434    if key.len() > MAX_METADATA_KEY_STRING_LEN {
435        return Err(MetadataKeyValidationError::InvalidLength {
436            max: MAX_METADATA_KEY_STRING_LEN,
437            actual: key.len(),
438        });
439    }
440
441    scrypto_encode(&key).map_err(|_| MetadataKeyValidationError::InvalidValue)
442}
443
444pub fn validate_metadata_value(
445    value: &MetadataValue,
446) -> Result<Vec<u8>, MetadataValueValidationError> {
447    let sbor_value = scrypto_encode(&MetadataEntryEntryPayload::from_content_source(
448        value.clone(),
449    ))
450    .map_err(|_| MetadataValueValidationError::InvalidValue)?;
451    if sbor_value.len() > MAX_METADATA_VALUE_SBOR_LEN {
452        return Err(MetadataValueValidationError::InvalidLength {
453            max: MAX_METADATA_VALUE_SBOR_LEN,
454            actual: sbor_value.len(),
455        });
456    }
457
458    match value {
459        MetadataValue::String(_) => {}
460        MetadataValue::Bool(_) => {}
461        MetadataValue::U8(_) => {}
462        MetadataValue::U32(_) => {}
463        MetadataValue::U64(_) => {}
464        MetadataValue::I32(_) => {}
465        MetadataValue::I64(_) => {}
466        MetadataValue::Decimal(_) => {}
467        MetadataValue::GlobalAddress(_) => {}
468        MetadataValue::PublicKey(_) => {}
469        MetadataValue::NonFungibleGlobalId(_) => {}
470        MetadataValue::NonFungibleLocalId(_) => {}
471        MetadataValue::Instant(_) => {}
472        MetadataValue::Url(url) => {
473            CheckedUrl::of(url.as_str()).ok_or(MetadataValueValidationError::InvalidURL(
474                url.as_str().to_owned(),
475            ))?;
476        }
477        MetadataValue::Origin(origin) => {
478            CheckedOrigin::of(origin.as_str()).ok_or(
479                MetadataValueValidationError::InvalidOrigin(origin.as_str().to_owned()),
480            )?;
481        }
482        MetadataValue::PublicKeyHash(_) => {}
483        MetadataValue::StringArray(_) => {}
484        MetadataValue::BoolArray(_) => {}
485        MetadataValue::U8Array(_) => {}
486        MetadataValue::U32Array(_) => {}
487        MetadataValue::U64Array(_) => {}
488        MetadataValue::I32Array(_) => {}
489        MetadataValue::I64Array(_) => {}
490        MetadataValue::DecimalArray(_) => {}
491        MetadataValue::GlobalAddressArray(_) => {}
492        MetadataValue::PublicKeyArray(_) => {}
493        MetadataValue::NonFungibleGlobalIdArray(_) => {}
494        MetadataValue::NonFungibleLocalIdArray(_) => {}
495        MetadataValue::InstantArray(_) => {}
496        MetadataValue::UrlArray(urls) => {
497            for url in urls {
498                CheckedUrl::of(url.as_str()).ok_or(MetadataValueValidationError::InvalidURL(
499                    url.as_str().to_owned(),
500                ))?;
501            }
502        }
503        MetadataValue::OriginArray(origins) => {
504            for origin in origins {
505                CheckedOrigin::of(origin.as_str()).ok_or(
506                    MetadataValueValidationError::InvalidOrigin(origin.as_str().to_owned()),
507                )?;
508            }
509        }
510        MetadataValue::PublicKeyHashArray(_) => {}
511    }
512
513    Ok(sbor_value)
514}