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    pub(crate) fn init_system_struct(
268        data: IndexMap<Vec<u8>, (Option<Vec<u8>>, bool)>,
269    ) -> Result<
270        (
271            IndexMap<u8, FieldValue>,
272            IndexMap<u8, IndexMap<Vec<u8>, KVEntry>>,
273        ),
274        MetadataError,
275    > {
276        let mut init_kv_entries = index_map_new();
277        for (key, entry) in data {
278            let kv_entry = KVEntry {
279                value: entry.0,
280                locked: entry.1,
281            };
282
283            init_kv_entries.insert(key, kv_entry);
284        }
285
286        Ok((
287            indexmap!(),
288            indexmap!(MetadataCollection::EntryKeyValue.collection_index() => init_kv_entries),
289        ))
290    }
291
292    pub(crate) fn create_with_data<Y: SystemApi<RuntimeError>>(
293        metadata_init: MetadataInit,
294        api: &mut Y,
295    ) -> Result<Own, RuntimeError> {
296        let metadata_validated = validate_metadata_init(metadata_init)?;
297        let (fields, kv_entries) = Self::init_system_struct(metadata_validated)
298            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::MetadataError(e)))?;
299
300        let node_id = api.new_object(
301            METADATA_BLUEPRINT,
302            vec![],
303            GenericArgs::default(),
304            fields,
305            kv_entries,
306        )?;
307
308        Ok(Own(node_id))
309    }
310
311    pub(crate) fn set<Y: SystemApi<RuntimeError>>(
312        key: String,
313        value: MetadataValue,
314        api: &mut Y,
315    ) -> Result<(), RuntimeError> {
316        let key_sbor = validate_metadata_key(&key).map_err(|e| {
317            RuntimeError::ApplicationError(ApplicationError::MetadataError(
318                MetadataError::MetadataKeyValidationError(e),
319            ))
320        })?;
321
322        let value_sbor = validate_metadata_value(&value).map_err(|e| {
323            RuntimeError::ApplicationError(ApplicationError::MetadataError(
324                MetadataError::MetadataValueValidationError(e),
325            ))
326        })?;
327
328        let handle = api.actor_open_key_value_entry(
329            ACTOR_STATE_SELF,
330            MetadataCollection::EntryKeyValue.collection_index(),
331            &key_sbor,
332            LockFlags::MUTABLE,
333        )?;
334        api.key_value_entry_set(handle, value_sbor)?;
335        api.key_value_entry_close(handle)?;
336
337        Runtime::emit_event(api, SetMetadataEvent { key, value })?;
338
339        Ok(())
340    }
341
342    pub(crate) fn lock<Y: SystemApi<RuntimeError>>(
343        key: String,
344        api: &mut Y,
345    ) -> Result<(), RuntimeError> {
346        let handle = api.actor_open_key_value_entry(
347            ACTOR_STATE_SELF,
348            MetadataCollection::EntryKeyValue.collection_index(),
349            &scrypto_encode(&key).unwrap(),
350            LockFlags::MUTABLE,
351        )?;
352        api.key_value_entry_lock(handle)?;
353        api.key_value_entry_close(handle)?;
354
355        Ok(())
356    }
357
358    pub(crate) fn get<Y: SystemApi<RuntimeError>>(
359        key: String,
360        api: &mut Y,
361    ) -> Result<Option<MetadataValue>, RuntimeError> {
362        let handle = api.actor_open_key_value_entry(
363            ACTOR_STATE_SELF,
364            MetadataCollection::EntryKeyValue.collection_index(),
365            &scrypto_encode(&key).unwrap(),
366            LockFlags::read_only(),
367        )?;
368
369        let data = api.key_value_entry_get(handle)?;
370        let substate: Option<MetadataEntryEntryPayload> = scrypto_decode(&data).unwrap();
371
372        Ok(substate.map(|v: MetadataEntryEntryPayload| v.fully_update_and_into_latest_version()))
373    }
374
375    pub(crate) fn remove<Y: SystemApi<RuntimeError>>(
376        key: String,
377        api: &mut Y,
378    ) -> Result<bool, RuntimeError> {
379        let cur_value: Option<MetadataEntryEntryPayload> = api.actor_remove_key_value_entry_typed(
380            ACTOR_STATE_SELF,
381            0u8,
382            &scrypto_encode(&key).unwrap(),
383        )?;
384        let rtn = cur_value.is_some();
385
386        Runtime::emit_event(api, RemoveMetadataEvent { key })?;
387
388        Ok(rtn)
389    }
390}
391
392#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
393pub enum MetadataKeyValidationError {
394    InvalidValue,
395    InvalidLength { max: usize, actual: usize },
396}
397
398#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
399pub enum MetadataValueValidationError {
400    InvalidURL(String),
401    InvalidOrigin(String),
402    InvalidValue,
403    InvalidLength { max: usize, actual: usize },
404}
405
406pub fn validate_metadata_init(
407    metadata_init: MetadataInit,
408) -> Result<IndexMap<Vec<u8>, (Option<Vec<u8>>, bool)>, RuntimeError> {
409    let mut checked_data = index_map_new();
410    for (key, value) in &metadata_init.data {
411        let key_sbor = validate_metadata_key(key).map_err(|e| {
412            RuntimeError::ApplicationError(ApplicationError::MetadataError(
413                MetadataError::MetadataKeyValidationError(e),
414            ))
415        })?;
416
417        if let Some(v) = &value.value {
418            let value_sbor = validate_metadata_value(&v).map_err(|e| {
419                RuntimeError::ApplicationError(ApplicationError::MetadataError(
420                    MetadataError::MetadataValueValidationError(e),
421                ))
422            })?;
423            checked_data.insert(key_sbor, (Some(value_sbor), value.lock));
424        } else {
425            checked_data.insert(key_sbor, (None, value.lock));
426        }
427    }
428    Ok(checked_data)
429}
430
431pub fn validate_metadata_key(key: &str) -> Result<Vec<u8>, MetadataKeyValidationError> {
432    if key.len() > MAX_METADATA_KEY_STRING_LEN {
433        return Err(MetadataKeyValidationError::InvalidLength {
434            max: MAX_METADATA_KEY_STRING_LEN,
435            actual: key.len(),
436        });
437    }
438
439    scrypto_encode(&key).map_err(|_| MetadataKeyValidationError::InvalidValue)
440}
441
442pub fn validate_metadata_value(
443    value: &MetadataValue,
444) -> Result<Vec<u8>, MetadataValueValidationError> {
445    let sbor_value = scrypto_encode(&MetadataEntryEntryPayload::from_content_source(
446        value.clone(),
447    ))
448    .map_err(|_| MetadataValueValidationError::InvalidValue)?;
449    if sbor_value.len() > MAX_METADATA_VALUE_SBOR_LEN {
450        return Err(MetadataValueValidationError::InvalidLength {
451            max: MAX_METADATA_VALUE_SBOR_LEN,
452            actual: sbor_value.len(),
453        });
454    }
455
456    match value {
457        MetadataValue::String(_) => {}
458        MetadataValue::Bool(_) => {}
459        MetadataValue::U8(_) => {}
460        MetadataValue::U32(_) => {}
461        MetadataValue::U64(_) => {}
462        MetadataValue::I32(_) => {}
463        MetadataValue::I64(_) => {}
464        MetadataValue::Decimal(_) => {}
465        MetadataValue::GlobalAddress(_) => {}
466        MetadataValue::PublicKey(_) => {}
467        MetadataValue::NonFungibleGlobalId(_) => {}
468        MetadataValue::NonFungibleLocalId(_) => {}
469        MetadataValue::Instant(_) => {}
470        MetadataValue::Url(url) => {
471            CheckedUrl::of(url.as_str()).ok_or(MetadataValueValidationError::InvalidURL(
472                url.as_str().to_owned(),
473            ))?;
474        }
475        MetadataValue::Origin(origin) => {
476            CheckedOrigin::of(origin.as_str()).ok_or(
477                MetadataValueValidationError::InvalidOrigin(origin.as_str().to_owned()),
478            )?;
479        }
480        MetadataValue::PublicKeyHash(_) => {}
481        MetadataValue::StringArray(_) => {}
482        MetadataValue::BoolArray(_) => {}
483        MetadataValue::U8Array(_) => {}
484        MetadataValue::U32Array(_) => {}
485        MetadataValue::U64Array(_) => {}
486        MetadataValue::I32Array(_) => {}
487        MetadataValue::I64Array(_) => {}
488        MetadataValue::DecimalArray(_) => {}
489        MetadataValue::GlobalAddressArray(_) => {}
490        MetadataValue::PublicKeyArray(_) => {}
491        MetadataValue::NonFungibleGlobalIdArray(_) => {}
492        MetadataValue::NonFungibleLocalIdArray(_) => {}
493        MetadataValue::InstantArray(_) => {}
494        MetadataValue::UrlArray(urls) => {
495            for url in urls {
496                CheckedUrl::of(url.as_str()).ok_or(MetadataValueValidationError::InvalidURL(
497                    url.as_str().to_owned(),
498                ))?;
499            }
500        }
501        MetadataValue::OriginArray(origins) => {
502            for origin in origins {
503                CheckedOrigin::of(origin.as_str()).ok_or(
504                    MetadataValueValidationError::InvalidOrigin(origin.as_str().to_owned()),
505                )?;
506            }
507        }
508        MetadataValue::PublicKeyHashArray(_) => {}
509    }
510
511    Ok(sbor_value)
512}