1use crate::errors::*;
2use crate::internal_prelude::*;
3use crate::system::system_callback::SystemBasedKernelApi;
4use crate::system::system_modules::costing::{apply_royalty_cost, RoyaltyRecipient};
5use radix_blueprint_schema_init::*;
6use radix_engine_interface::api::field_api::LockFlags;
7use radix_engine_interface::api::*;
8use radix_engine_interface::object_modules::royalty::*;
9use radix_native_sdk::resource::NativeVault;
10
11use crate::blueprints::package::PackageError;
13use crate::roles_template;
14use crate::system::system_callback::*;
15use crate::system::system_substates::FieldSubstate;
16use crate::system::system_substates::KeyValueEntrySubstate;
17use radix_engine_interface::blueprints::package::*;
18
19declare_native_blueprint_state! {
20 blueprint_ident: ComponentRoyalty,
21 blueprint_snake_case: component_royalty,
22 features: {
23 },
24 fields: {
25 accumulator: {
26 ident: Accumulator,
27 field_type: {
28 kind: StaticSingleVersioned,
29 },
30 condition: Condition::Always,
31 },
32 },
33 collections: {
34 method_royalties: KeyValue {
35 entry_ident: MethodAmount,
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 ComponentRoyaltyAccumulatorV1 = ComponentRoyaltySubstate;
49pub type ComponentRoyaltyMethodAmountV1 = RoyaltyAmount;
50
51pub struct RoyaltyNativePackage;
52impl RoyaltyNativePackage {
53 pub fn definition() -> PackageDefinition {
54 let mut aggregator = TypeAggregator::<ScryptoCustomTypeKind>::new();
55
56 let state = ComponentRoyaltyStateSchemaInit::create_schema_init(&mut aggregator);
57
58 let mut functions = index_map_new();
59 functions.insert(
60 COMPONENT_ROYALTY_CREATE_IDENT.to_string(),
61 FunctionSchemaInit {
62 receiver: None,
63 input: TypeRef::Static(
64 aggregator.add_child_type_and_descendents::<ComponentRoyaltyCreateInput>(),
65 ),
66 output: TypeRef::Static(
67 aggregator.add_child_type_and_descendents::<ComponentRoyaltyCreateOutput>(),
68 ),
69 export: COMPONENT_ROYALTY_CREATE_IDENT.to_string(),
70 },
71 );
72 functions.insert(
73 COMPONENT_ROYALTY_SET_ROYALTY_IDENT.to_string(),
74 FunctionSchemaInit {
75 receiver: Some(ReceiverInfo::normal_ref_mut()),
76 input: TypeRef::Static(
77 aggregator.add_child_type_and_descendents::<ComponentRoyaltySetInput>(),
78 ),
79 output: TypeRef::Static(
80 aggregator.add_child_type_and_descendents::<ComponentRoyaltySetOutput>(),
81 ),
82 export: COMPONENT_ROYALTY_SET_ROYALTY_IDENT.to_string(),
83 },
84 );
85 functions.insert(
86 COMPONENT_ROYALTY_LOCK_ROYALTY_IDENT.to_string(),
87 FunctionSchemaInit {
88 receiver: Some(ReceiverInfo::normal_ref_mut()),
89 input: TypeRef::Static(
90 aggregator.add_child_type_and_descendents::<ComponentRoyaltyLockInput>(),
91 ),
92 output: TypeRef::Static(
93 aggregator.add_child_type_and_descendents::<ComponentRoyaltyLockOutput>(),
94 ),
95 export: COMPONENT_ROYALTY_LOCK_ROYALTY_IDENT.to_string(),
96 },
97 );
98 functions.insert(
99 COMPONENT_ROYALTY_CLAIM_ROYALTIES_IDENT.to_string(),
100 FunctionSchemaInit {
101 receiver: Some(ReceiverInfo::normal_ref_mut()),
102 input: TypeRef::Static(
103 aggregator.add_child_type_and_descendents::<ComponentClaimRoyaltiesInput>(),
104 ),
105 output: TypeRef::Static(
106 aggregator.add_child_type_and_descendents::<ComponentClaimRoyaltiesOutput>(),
107 ),
108 export: COMPONENT_ROYALTY_CLAIM_ROYALTIES_IDENT.to_string(),
109 },
110 );
111
112 let schema = generate_full_schema(aggregator);
113
114 let blueprints = indexmap!(
115 COMPONENT_ROYALTY_BLUEPRINT.to_string() => BlueprintDefinitionInit {
116 blueprint_type: BlueprintType::default(),
117 is_transient: true,
118 feature_set: indexset!(),
119 dependencies: indexset!(XRD.into(),),
120
121 schema: BlueprintSchemaInit {
122 generics: vec![],
123 schema,
124 state,
125 events: BlueprintEventSchemaInit::default(),
126 types: BlueprintTypeSchemaInit::default(),
127 functions: BlueprintFunctionsSchemaInit {
128 functions,
129 },
130 hooks: BlueprintHooksInit::default(),
131 },
132
133 royalty_config: PackageRoyaltyConfig::default(),
134 auth_config: AuthConfig {
135 function_auth: FunctionAuth::AllowAll,
136 method_auth: MethodAuthTemplate::StaticRoleDefinition(
137 roles_template!(
138 roles {
139 COMPONENT_ROYALTY_SETTER_ROLE => updaters: [COMPONENT_ROYALTY_SETTER_UPDATER_ROLE];
140 COMPONENT_ROYALTY_SETTER_UPDATER_ROLE => updaters: [COMPONENT_ROYALTY_SETTER_UPDATER_ROLE];
141 COMPONENT_ROYALTY_LOCKER_ROLE => updaters: [COMPONENT_ROYALTY_LOCKER_UPDATER_ROLE];
142 COMPONENT_ROYALTY_LOCKER_UPDATER_ROLE => updaters: [COMPONENT_ROYALTY_LOCKER_UPDATER_ROLE];
143 COMPONENT_ROYALTY_CLAIMER_ROLE => updaters: [COMPONENT_ROYALTY_CLAIMER_UPDATER_ROLE];
144 COMPONENT_ROYALTY_CLAIMER_UPDATER_ROLE => updaters: [COMPONENT_ROYALTY_CLAIMER_UPDATER_ROLE];
145 },
146 methods {
147 COMPONENT_ROYALTY_CLAIM_ROYALTIES_IDENT => [COMPONENT_ROYALTY_CLAIMER_ROLE];
148 COMPONENT_ROYALTY_SET_ROYALTY_IDENT => [COMPONENT_ROYALTY_SETTER_ROLE];
149 COMPONENT_ROYALTY_LOCK_ROYALTY_IDENT => [COMPONENT_ROYALTY_LOCKER_ROLE];
150 }
151 ),
152 ),
153 },
154 },
155 );
156
157 PackageDefinition { blueprints }
158 }
159
160 pub fn invoke_export<Y: SystemApi<RuntimeError>>(
161 export_name: &str,
162 input: &IndexedScryptoValue,
163 api: &mut Y,
164 ) -> Result<IndexedScryptoValue, RuntimeError> {
165 match export_name {
166 COMPONENT_ROYALTY_CREATE_IDENT => {
167 let input: ComponentRoyaltyCreateInput = input.as_typed().map_err(|e| {
168 RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
169 })?;
170 let rtn = ComponentRoyaltyBlueprint::create(input.royalty_config, api)?;
171 Ok(IndexedScryptoValue::from_typed(&rtn))
172 }
173 COMPONENT_ROYALTY_SET_ROYALTY_IDENT => {
174 let input: ComponentRoyaltySetInput = input.as_typed().map_err(|e| {
175 RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
176 })?;
177 let rtn = ComponentRoyaltyBlueprint::set_royalty(input.method, input.amount, api)?;
178
179 Ok(IndexedScryptoValue::from_typed(&rtn))
180 }
181 COMPONENT_ROYALTY_LOCK_ROYALTY_IDENT => {
182 let input: ComponentRoyaltyLockInput = input.as_typed().map_err(|e| {
183 RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
184 })?;
185 let rtn = ComponentRoyaltyBlueprint::lock_royalty(input.method, api)?;
186
187 Ok(IndexedScryptoValue::from_typed(&rtn))
188 }
189 COMPONENT_ROYALTY_CLAIM_ROYALTIES_IDENT => {
190 let _input: ComponentClaimRoyaltiesInput = input.as_typed().map_err(|e| {
191 RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
192 })?;
193 let rtn = ComponentRoyaltyBlueprint::claim_royalties(api)?;
194 Ok(IndexedScryptoValue::from_typed(&rtn))
195 }
196 _ => Err(RuntimeError::ApplicationError(
197 ApplicationError::ExportDoesNotExist(export_name.to_string()),
198 )),
199 }
200 }
201}
202
203#[derive(Debug, Clone, Eq, PartialEq, ScryptoSbor)]
204pub enum ComponentRoyaltyError {
205 RoyaltyAmountIsGreaterThanAllowed {
206 max: RoyaltyAmount,
207 actual: RoyaltyAmount,
208 },
209 UnexpectedDecimalComputationError,
210 RoyaltyAmountIsNegative(RoyaltyAmount),
211}
212
213pub struct RoyaltyUtil;
214
215impl RoyaltyUtil {
216 pub fn verify_royalty_amounts<'a, Y: SystemApi<RuntimeError>>(
217 royalty_amounts: impl Iterator<Item = &'a RoyaltyAmount>,
218 is_component: bool,
219 api: &mut Y,
220 ) -> Result<(), RuntimeError> {
221 let max_royalty_in_xrd = match api.max_per_function_royalty_in_xrd() {
222 Ok(amount) => Ok(amount),
223 Err(RuntimeError::SystemError(SystemError::CostingModuleNotEnabled)) => return Ok(()),
224 e => e,
225 }?;
226 let max_royalty_in_usd = max_royalty_in_xrd.checked_div(api.usd_price()?).ok_or(
227 RuntimeError::ApplicationError(ApplicationError::ComponentRoyaltyError(
228 ComponentRoyaltyError::UnexpectedDecimalComputationError,
229 )),
230 )?;
231
232 for royalty_amount in royalty_amounts {
233 if royalty_amount.is_negative() {
235 if is_component {
236 return Err(RuntimeError::ApplicationError(
237 ApplicationError::ComponentRoyaltyError(
238 ComponentRoyaltyError::RoyaltyAmountIsNegative(*royalty_amount),
239 ),
240 ));
241 } else {
242 return Err(RuntimeError::ApplicationError(
243 ApplicationError::PackageError(PackageError::RoyaltyAmountIsNegative(
244 *royalty_amount,
245 )),
246 ));
247 }
248 }
249
250 match royalty_amount {
251 RoyaltyAmount::Free => {}
252 RoyaltyAmount::Xrd(xrd_amount) => {
253 if xrd_amount.gt(&max_royalty_in_xrd) {
254 if is_component {
255 return Err(RuntimeError::ApplicationError(
256 ApplicationError::ComponentRoyaltyError(
257 ComponentRoyaltyError::RoyaltyAmountIsGreaterThanAllowed {
258 max: RoyaltyAmount::Xrd(max_royalty_in_xrd),
259 actual: royalty_amount.clone(),
260 },
261 ),
262 ));
263 } else {
264 return Err(RuntimeError::ApplicationError(
265 ApplicationError::PackageError(
266 PackageError::RoyaltyAmountIsGreaterThanAllowed {
267 max: RoyaltyAmount::Xrd(max_royalty_in_xrd),
268 actual: royalty_amount.clone(),
269 },
270 ),
271 ));
272 }
273 }
274 }
275 RoyaltyAmount::Usd(usd_amount) => {
276 if usd_amount.gt(&max_royalty_in_usd) {
277 if is_component {
278 return Err(RuntimeError::ApplicationError(
279 ApplicationError::ComponentRoyaltyError(
280 ComponentRoyaltyError::RoyaltyAmountIsGreaterThanAllowed {
281 max: RoyaltyAmount::Usd(max_royalty_in_usd),
282 actual: royalty_amount.clone(),
283 },
284 ),
285 ));
286 } else {
287 return Err(RuntimeError::ApplicationError(
288 ApplicationError::PackageError(
289 PackageError::RoyaltyAmountIsGreaterThanAllowed {
290 max: RoyaltyAmount::Xrd(max_royalty_in_xrd),
291 actual: royalty_amount.clone(),
292 },
293 ),
294 ));
295 }
296 }
297 }
298 }
299 }
300
301 Ok(())
302 }
303}
304
305pub struct ComponentRoyaltyBlueprint;
306
307impl ComponentRoyaltyBlueprint {
308 pub(crate) fn create(
309 royalty_config: ComponentRoyaltyConfig,
310 api: &mut impl SystemApi<RuntimeError>,
311 ) -> Result<Own, RuntimeError> {
312 let accumulator_substate = ComponentRoyaltySubstate {
314 royalty_vault: Vault::create(XRD, api)?,
315 };
316
317 let mut kv_entries = index_map_new();
318 {
319 RoyaltyUtil::verify_royalty_amounts(
320 royalty_config
321 .royalty_amounts
322 .values()
323 .map(|(amount, _locked)| amount),
324 true,
325 api,
326 )?;
327
328 let mut royalty_config_entries = index_map_new();
329 for (method, (amount, locked)) in royalty_config.royalty_amounts {
330 let kv_entry = KVEntry {
331 value: Some(
332 scrypto_encode(
333 &ComponentRoyaltyMethodAmountEntryPayload::from_content_source(amount),
334 )
335 .unwrap(),
336 ),
337 locked,
338 };
339 royalty_config_entries.insert(scrypto_encode(&method).unwrap(), kv_entry);
340 }
341 kv_entries.insert(
342 ComponentRoyaltyCollection::MethodAmountKeyValue.collection_index(),
343 royalty_config_entries,
344 );
345 }
346
347 let component_id = api.new_object(
348 COMPONENT_ROYALTY_BLUEPRINT,
349 vec![],
350 GenericArgs::default(),
351 indexmap! {
352 ComponentRoyaltyField::Accumulator.field_index() => FieldValue::immutable(&ComponentRoyaltyAccumulatorFieldPayload::from_content_source(accumulator_substate)),
353 },
354 kv_entries,
355 )?;
356
357 Ok(Own(component_id))
358 }
359
360 pub(crate) fn set_royalty(
361 method: String,
362 amount: RoyaltyAmount,
363 api: &mut impl SystemApi<RuntimeError>,
364 ) -> Result<(), RuntimeError> {
365 RoyaltyUtil::verify_royalty_amounts(vec![amount.clone()].iter(), true, api)?;
366
367 let handle = api.actor_open_key_value_entry(
368 ACTOR_STATE_SELF,
369 ComponentRoyaltyCollection::MethodAmountKeyValue.collection_index(),
370 &scrypto_encode(&method).unwrap(),
371 LockFlags::MUTABLE,
372 )?;
373 api.key_value_entry_set_typed(
374 handle,
375 ComponentRoyaltyMethodAmountEntryPayload::from_content_source(amount),
376 )?;
377 api.key_value_entry_close(handle)?;
378
379 Ok(())
380 }
381
382 pub(crate) fn lock_royalty(
383 method: String,
384 api: &mut impl SystemApi<RuntimeError>,
385 ) -> Result<(), RuntimeError> {
386 let handle = api.actor_open_key_value_entry(
387 ACTOR_STATE_SELF,
388 ComponentRoyaltyCollection::MethodAmountKeyValue.collection_index(),
389 &scrypto_encode(&method).unwrap(),
390 LockFlags::MUTABLE,
391 )?;
392 api.key_value_entry_lock(handle)?;
393 api.key_value_entry_close(handle)?;
394
395 Ok(())
396 }
397
398 pub(crate) fn claim_royalties(
399 api: &mut impl SystemApi<RuntimeError>,
400 ) -> Result<Bucket, RuntimeError> {
401 let handle = api.actor_open_field(
402 ACTOR_STATE_SELF,
403 RoyaltyField::RoyaltyAccumulator.into(),
404 LockFlags::read_only(),
405 )?;
406
407 let substate = api
408 .field_read_typed::<ComponentRoyaltyAccumulatorFieldPayload>(handle)?
409 .fully_update_and_into_latest_version();
410 let mut royalty_vault = substate.royalty_vault;
411 let bucket = royalty_vault.take_all(api)?;
412 api.field_close(handle)?;
413
414 Ok(bucket)
415 }
416
417 pub fn charge_component_royalty(
418 receiver: &NodeId,
419 ident: &str,
420 api: &mut impl SystemBasedKernelApi,
421 ) -> Result<(), RuntimeError> {
422 let accumulator_handle = api.kernel_open_substate(
423 receiver,
424 ROYALTY_BASE_PARTITION
425 .at_offset(ROYALTY_FIELDS_PARTITION_OFFSET)
426 .unwrap(),
427 &RoyaltyField::RoyaltyAccumulator.into(),
428 LockFlags::read_only(),
429 SystemLockData::default(),
430 )?;
431 let component_royalty: FieldSubstate<ComponentRoyaltyAccumulatorFieldPayload> = api
432 .kernel_read_substate(accumulator_handle)?
433 .as_typed()
434 .unwrap();
435
436 let component_royalty = component_royalty
437 .into_payload()
438 .fully_update_and_into_latest_version();
439
440 let royalty_charge = {
441 let handle = api.kernel_open_substate_with_default(
442 receiver,
443 ROYALTY_BASE_PARTITION
444 .at_offset(ROYALTY_CONFIG_PARTITION_OFFSET)
445 .unwrap(),
446 &SubstateKey::Map(scrypto_encode(ident).unwrap()),
447 LockFlags::read_only(),
448 Some(|| {
449 let kv_entry =
450 KeyValueEntrySubstate::<ComponentRoyaltyMethodAmountEntryPayload>::default(
451 );
452 IndexedScryptoValue::from_typed(&kv_entry)
453 }),
454 SystemLockData::default(),
455 )?;
456
457 let substate: KeyValueEntrySubstate<ComponentRoyaltyMethodAmountEntryPayload> =
458 api.kernel_read_substate(handle)?.as_typed().unwrap();
459 api.kernel_close_substate(handle)?;
460 substate
461 .into_value()
462 .map(|v| v.fully_update_and_into_latest_version())
463 .unwrap_or(RoyaltyAmount::Free)
464 };
465
466 assert!(!royalty_charge.is_negative());
469
470 if royalty_charge.is_non_zero() {
471 let vault_id = component_royalty.royalty_vault.0;
472 let component_address = ComponentAddress::new_or_panic(receiver.0);
473
474 apply_royalty_cost(
475 &mut api.system_module_api(),
476 royalty_charge,
477 RoyaltyRecipient::Component(component_address, vault_id.into()),
478 )?;
479 }
480
481 api.kernel_close_substate(accumulator_handle)?;
482
483 Ok(())
484 }
485}