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 #[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}