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