radix_engine/object_modules/royalty/
package.rs

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
11// Re-export substates
12use 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            // Disallow negative royalties, 0 is acceptable.
234            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        // Create a royalty vault
313        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        // We check for negative royalties at the instantiation time of the royalty module,
467        // and whenever the royalty amount is updated
468        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}