radix_engine/blueprints/consensus_manager/
validator.rs

1use crate::blueprints::consensus_manager::*;
2use crate::blueprints::util::SecurifiedRoleAssignment;
3use crate::errors::ApplicationError;
4use crate::errors::RuntimeError;
5use crate::internal_prelude::*;
6use crate::{event_schema, roles_template};
7use radix_engine_interface::api::field_api::LockFlags;
8use radix_engine_interface::api::{
9    AttachedModuleId, FieldValue, SystemApi, ACTOR_REF_GLOBAL, ACTOR_STATE_OUTER_OBJECT,
10    ACTOR_STATE_SELF,
11};
12use radix_engine_interface::blueprints::consensus_manager::*;
13use radix_engine_interface::blueprints::package::{
14    AuthConfig, BlueprintDefinitionInit, BlueprintType, FunctionAuth, MethodAuthTemplate,
15};
16use radix_engine_interface::blueprints::resource::*;
17use radix_engine_interface::object_modules::metadata::UncheckedUrl;
18use radix_engine_interface::{burn_roles, metadata_init, mint_roles, rule};
19use radix_native_sdk::modules::metadata::Metadata;
20use radix_native_sdk::resource::NativeVault;
21use radix_native_sdk::resource::ResourceManager;
22use radix_native_sdk::resource::{NativeBucket, NativeNonFungibleBucket};
23use radix_native_sdk::runtime::Runtime;
24use sbor::rust::mem;
25
26use super::{
27    ClaimXrdEvent, RegisterValidatorEvent, StakeEvent, UnregisterValidatorEvent, UnstakeEvent,
28    UpdateAcceptingStakeDelegationStateEvent,
29};
30
31pub const VALIDATOR_PROTOCOL_VERSION_NAME_LEN: usize = 32;
32
33/// A performance-driven limit on the number of simultaneously pending "delayed withdrawal"
34/// operations on any validator's owner's stake units vault.
35pub const OWNER_STAKE_UNITS_PENDING_WITHDRAWALS_LIMIT: usize = 100;
36
37#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
38pub struct ValidatorSubstate {
39    /// A key used internally for storage of registered validators sorted by their stake descending.
40    /// It is only useful when the validator is registered and has non-zero stake - hence, the field
41    /// is [`None`] otherwise.
42    /// Note: in theory, this value could be always computed from the [`is_registered`] status and
43    /// the amount stored in [`stake_xrd_vault_id`]; we simply keep it cached to simplify certain
44    /// updates.
45    pub sorted_key: Option<SortedKey>,
46
47    /// This validator's public key.
48    pub key: Secp256k1PublicKey,
49
50    /// Whether this validator is currently interested in participating in the consensus.
51    pub is_registered: bool,
52
53    /// Whether this validator is currently accepting delegated stake or not
54    pub accepts_delegated_stake: bool,
55
56    /// A fraction of the effective emission amount which gets transferred to the validator's owner
57    /// (by staking it and depositing the stake units to the [`locked_owner_stake_unit_vault_id`]).
58    /// Note: it is a decimal factor, not a percentage (i.e. `0.015` means "1.5%" here).
59    /// Note: it may be overridden by [`validator_fee_change_request`], if it contains a change
60    /// which already became effective.
61    pub validator_fee_factor: Decimal,
62
63    /// The most recent request to change the [`validator_fee_factor`] (which requires a delay).
64    /// Note: the value from this request will be used instead of [`validator_fee_factor`] if the
65    /// request has already reached its effective epoch.
66    /// Note: when another change is requested, the value from this (previous) one is moved to the
67    /// [`validator_fee_factor`] - provided that it became already effective. Otherwise, this
68    /// request is overwritten by the new one.
69    pub validator_fee_change_request: Option<ValidatorFeeChangeRequest>,
70
71    /// A type of fungible resource representing stake units specific to this validator.
72    /// Conceptually, "staking to validator A" means "contributing to the validator's staking pool,
73    /// and receiving the validator's stake units which act as the pool units for the staking pool".
74    pub stake_unit_resource: ResourceAddress,
75
76    /// A vault holding the XRDs currently staked to this validator.
77    pub stake_xrd_vault_id: Own,
78
79    /// A type of non-fungible token used as a receipt for unstaked stake units.
80    /// Unstaking burns the SUs and inactivates the staked XRDs (i.e. moves it from the regular
81    /// [`stake_xrd_vault_id`] to the [`pending_xrd_withdraw_vault_id`]), and then requires to claim
82    /// the XRDs using this NFT after a delay (see [`UnstakeData.claim_epoch`]).
83    pub claim_nft: ResourceAddress,
84
85    /// A vault holding the XRDs that were unstaked (see the [`unstake_nft`]) but not yet claimed.
86    pub pending_xrd_withdraw_vault_id: Own,
87
88    /// A vault holding the SUs that this validator's owner voluntarily decided to temporarily lock
89    /// here, as a public display of their confidence in this validator's future reliability.
90    /// Withdrawing SUs from this vault is subject to a delay (which is configured separately from
91    /// the regular unstaking delay, see [`ConsensusManagerConfigSubstate.num_owner_stake_units_unlock_epochs`]).
92    /// This vault is private to the owner (i.e. the owner's badge is required for any interaction
93    /// with this vault).
94    pub locked_owner_stake_unit_vault_id: Own,
95
96    /// A vault holding the SUs which the owner has decided to withdraw from their "public display"
97    /// vault (see [`locked_owner_stake_unit_vault_id`]) but which have not yet been unlocked after
98    /// the mandatory delay (see [`pending_owner_stake_unit_withdrawals`]).
99    pub pending_owner_stake_unit_unlock_vault_id: Own,
100
101    /// All currently pending "delayed withdrawal" operations of the owner's stake units vault (see
102    /// [`locked_owner_stake_unit_vault_id`]).
103    /// This maps an epoch number to an amount of stake units that become unlocked at that epoch.
104    /// Note: because of performance considerations, a maximum size of this map is limited to
105    /// [`OWNER_STAKE_UNITS_PENDING_WITHDRAWALS_LIMIT`]: starting another withdrawal will first
106    /// attempt to move any already-available amount to [`already_unlocked_owner_stake_unit_amount`]
107    /// and only then will fail if the limit is exceeded.
108    pub pending_owner_stake_unit_withdrawals: BTreeMap<Epoch, Decimal>,
109
110    /// An amount of owner's stake units that has already waited for a sufficient number of epochs
111    /// in the [`pending_owner_stake_unit_withdrawals`] and was automatically moved from there.
112    /// The very next [`finish_unlock_owner_stake_units()`] operation will release this amount.
113    pub already_unlocked_owner_stake_unit_amount: Decimal,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
117#[sbor(transparent)]
118pub struct ValidatorProtocolUpdateReadinessSignalSubstate {
119    pub protocol_version_name: Option<String>,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor, ManifestSbor)]
123pub struct UnstakeData {
124    pub name: String,
125
126    /// An epoch number at (or after) which the pending unstaked XRD may be claimed.
127    /// Note: on unstake, it is fixed to be [`ConsensusManagerConfigSubstate.num_unstake_epochs`] away.
128    pub claim_epoch: Epoch,
129
130    /// An XRD amount to be claimed.
131    pub claim_amount: Decimal,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
135pub struct ValidatorFeeChangeRequest {
136    /// An epoch number at (or after) which the fee change is effective.
137    /// To be specific: when a next epoch `N` begins, we perform accounting of emissions due for
138    /// previous epoch `N-1` - this means that we will use this [`new_validator_fee_factor`] only if
139    /// `epoch_effective <= N-1`, and [`ValidatorSubstate.validator_fee_factor`] otherwise.
140    /// Note: when requesting a fee decrease, this will be "next epoch"; and when requesting an
141    /// increase, this will be set to [`ConsensusManagerConfigSubstate.num_fee_increase_delay_epochs`]
142    /// epochs away.
143    pub epoch_effective: Epoch,
144
145    /// A requested new value of [`ConsensusManagerSubstate.validator_fee_factor`].
146    pub new_fee_factor: Decimal,
147}
148
149impl NonFungibleData for UnstakeData {
150    const MUTABLE_FIELDS: &'static [&'static str] = &[];
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
154pub enum ValidatorError {
155    InvalidClaimResource,
156    InvalidGetRedemptionAmount,
157    UnexpectedDecimalComputationError,
158    EpochUnlockHasNotOccurredYet,
159    PendingOwnerStakeWithdrawalLimitReached,
160    InvalidValidatorFeeFactor,
161    ValidatorIsNotAcceptingDelegatedStake,
162    InvalidProtocolVersionNameLength { expected: usize, actual: usize },
163    EpochMathOverflow,
164}
165
166declare_native_blueprint_state! {
167    blueprint_ident: Validator,
168    blueprint_snake_case: validator,
169    features: {
170    },
171    fields: {
172        state: {
173            ident: State,
174            field_type: {
175                kind: StaticSingleVersioned,
176            },
177            condition: Condition::Always,
178        },
179        protocol_update_readiness_signal: {
180            ident: ProtocolUpdateReadinessSignal,
181            field_type: {
182                kind: StaticSingleVersioned,
183            },
184            condition: Condition::Always,
185        },
186    },
187    collections: {
188    }
189}
190
191pub type ValidatorStateV1 = ValidatorSubstate;
192pub type ValidatorProtocolUpdateReadinessSignalV1 = ValidatorProtocolUpdateReadinessSignalSubstate;
193
194#[derive(Debug, Clone, Eq, PartialEq, ScryptoSbor, ManifestSbor)]
195enum UpdateSecondaryIndex {
196    Create {
197        index_key: SortedKey,
198        key: Secp256k1PublicKey,
199        stake: Decimal,
200    },
201    UpdateStake {
202        index_key: SortedKey,
203        new_index_key: SortedKey,
204        new_stake_amount: Decimal,
205    },
206    UpdatePublicKey {
207        index_key: SortedKey,
208        key: Secp256k1PublicKey,
209    },
210    Remove {
211        index_key: SortedKey,
212    },
213}
214
215pub struct ValidatorBlueprint;
216
217impl ValidatorBlueprint {
218    pub fn definition() -> BlueprintDefinitionInit {
219        let mut aggregator = TypeAggregator::<ScryptoCustomTypeKind>::new();
220
221        let feature_set = ValidatorFeatureSet::all_features();
222        let state = ValidatorStateSchemaInit::create_schema_init(&mut aggregator);
223
224        let mut functions = index_map_new();
225        functions.insert(
226            VALIDATOR_REGISTER_IDENT.to_string(),
227            FunctionSchemaInit {
228                receiver: Some(ReceiverInfo::normal_ref_mut()),
229                input: TypeRef::Static(
230                    aggregator.add_child_type_and_descendents::<ValidatorRegisterInput>(),
231                ),
232                output: TypeRef::Static(
233                    aggregator.add_child_type_and_descendents::<ValidatorRegisterOutput>(),
234                ),
235                export: VALIDATOR_REGISTER_IDENT.to_string(),
236            },
237        );
238        functions.insert(
239            VALIDATOR_UNREGISTER_IDENT.to_string(),
240            FunctionSchemaInit {
241                receiver: Some(ReceiverInfo::normal_ref_mut()),
242                input: TypeRef::Static(
243                    aggregator.add_child_type_and_descendents::<ValidatorUnregisterInput>(),
244                ),
245                output: TypeRef::Static(
246                    aggregator.add_child_type_and_descendents::<ValidatorUnregisterOutput>(),
247                ),
248                export: VALIDATOR_UNREGISTER_IDENT.to_string(),
249            },
250        );
251        functions.insert(
252            VALIDATOR_STAKE_AS_OWNER_IDENT.to_string(),
253            FunctionSchemaInit {
254                receiver: Some(ReceiverInfo::normal_ref_mut()),
255                input: TypeRef::Static(
256                    aggregator.add_child_type_and_descendents::<ValidatorStakeAsOwnerInput>(),
257                ),
258                output: TypeRef::Static(
259                    aggregator.add_child_type_and_descendents::<ValidatorStakeAsOwnerOutput>(),
260                ),
261                export: VALIDATOR_STAKE_AS_OWNER_IDENT.to_string(),
262            },
263        );
264        functions.insert(
265            VALIDATOR_STAKE_IDENT.to_string(),
266            FunctionSchemaInit {
267                receiver: Some(ReceiverInfo::normal_ref_mut()),
268                input: TypeRef::Static(
269                    aggregator.add_child_type_and_descendents::<ValidatorStakeInput>(),
270                ),
271                output: TypeRef::Static(
272                    aggregator.add_child_type_and_descendents::<ValidatorStakeOutput>(),
273                ),
274                export: VALIDATOR_STAKE_IDENT.to_string(),
275            },
276        );
277        functions.insert(
278            VALIDATOR_UNSTAKE_IDENT.to_string(),
279            FunctionSchemaInit {
280                receiver: Some(ReceiverInfo::normal_ref_mut()),
281                input: TypeRef::Static(
282                    aggregator.add_child_type_and_descendents::<ValidatorUnstakeInput>(),
283                ),
284                output: TypeRef::Static(
285                    aggregator.add_child_type_and_descendents::<ValidatorUnstakeOutput>(),
286                ),
287                export: VALIDATOR_UNSTAKE_IDENT.to_string(),
288            },
289        );
290        functions.insert(
291            VALIDATOR_CLAIM_XRD_IDENT.to_string(),
292            FunctionSchemaInit {
293                receiver: Some(ReceiverInfo::normal_ref_mut()),
294                input: TypeRef::Static(
295                    aggregator.add_child_type_and_descendents::<ValidatorClaimXrdInput>(),
296                ),
297                output: TypeRef::Static(
298                    aggregator.add_child_type_and_descendents::<ValidatorClaimXrdOutput>(),
299                ),
300                export: VALIDATOR_CLAIM_XRD_IDENT.to_string(),
301            },
302        );
303        functions.insert(
304            VALIDATOR_UPDATE_KEY_IDENT.to_string(),
305            FunctionSchemaInit {
306                receiver: Some(ReceiverInfo::normal_ref_mut()),
307                input: TypeRef::Static(
308                    aggregator.add_child_type_and_descendents::<ValidatorUpdateKeyInput>(),
309                ),
310                output: TypeRef::Static(
311                    aggregator.add_child_type_and_descendents::<ValidatorUpdateKeyOutput>(),
312                ),
313                export: VALIDATOR_UPDATE_KEY_IDENT.to_string(),
314            },
315        );
316        functions.insert(
317            VALIDATOR_UPDATE_FEE_IDENT.to_string(),
318            FunctionSchemaInit {
319                receiver: Some(ReceiverInfo::normal_ref_mut()),
320                input: TypeRef::Static(
321                    aggregator.add_child_type_and_descendents::<ValidatorUpdateFeeInput>(),
322                ),
323                output: TypeRef::Static(
324                    aggregator.add_child_type_and_descendents::<ValidatorUpdateFeeOutput>(),
325                ),
326                export: VALIDATOR_UPDATE_FEE_IDENT.to_string(),
327            },
328        );
329        functions.insert(
330            VALIDATOR_UPDATE_ACCEPT_DELEGATED_STAKE_IDENT.to_string(),
331            FunctionSchemaInit {
332                receiver: Some(ReceiverInfo::normal_ref_mut()),
333                input: TypeRef::Static(aggregator
334                    .add_child_type_and_descendents::<ValidatorUpdateAcceptDelegatedStakeInput>()),
335                output: TypeRef::Static(aggregator
336                    .add_child_type_and_descendents::<ValidatorUpdateAcceptDelegatedStakeOutput>()),
337                export: VALIDATOR_UPDATE_ACCEPT_DELEGATED_STAKE_IDENT.to_string(),
338            },
339        );
340        functions.insert(
341            VALIDATOR_ACCEPTS_DELEGATED_STAKE_IDENT.to_string(),
342            FunctionSchemaInit {
343                receiver: Some(ReceiverInfo::normal_ref_mut()),
344                input: TypeRef::Static(
345                    aggregator
346                        .add_child_type_and_descendents::<ValidatorAcceptsDelegatedStakeInput>(),
347                ),
348                output: TypeRef::Static(
349                    aggregator
350                        .add_child_type_and_descendents::<ValidatorAcceptsDelegatedStakeOutput>(),
351                ),
352                export: VALIDATOR_ACCEPTS_DELEGATED_STAKE_IDENT.to_string(),
353            },
354        );
355        functions.insert(
356            VALIDATOR_TOTAL_STAKE_XRD_AMOUNT_IDENT.to_string(),
357            FunctionSchemaInit {
358                receiver: Some(ReceiverInfo::normal_ref()),
359                input: TypeRef::Static(
360                    aggregator
361                        .add_child_type_and_descendents::<ValidatorTotalStakeXrdAmountInput>(),
362                ),
363                output: TypeRef::Static(
364                    aggregator
365                        .add_child_type_and_descendents::<ValidatorTotalStakeXrdAmountOutput>(),
366                ),
367                export: VALIDATOR_TOTAL_STAKE_XRD_AMOUNT_IDENT.to_string(),
368            },
369        );
370        functions.insert(
371            VALIDATOR_TOTAL_STAKE_UNIT_SUPPLY_IDENT.to_string(),
372            FunctionSchemaInit {
373                receiver: Some(ReceiverInfo::normal_ref()),
374                input: TypeRef::Static(
375                    aggregator
376                        .add_child_type_and_descendents::<ValidatorTotalStakeUnitSupplyInput>(),
377                ),
378                output: TypeRef::Static(
379                    aggregator
380                        .add_child_type_and_descendents::<ValidatorTotalStakeUnitSupplyOutput>(),
381                ),
382                export: VALIDATOR_TOTAL_STAKE_UNIT_SUPPLY_IDENT.to_string(),
383            },
384        );
385        functions.insert(
386            VALIDATOR_GET_REDEMPTION_VALUE_IDENT.to_string(),
387            FunctionSchemaInit {
388                receiver: Some(ReceiverInfo::normal_ref()),
389                input: TypeRef::Static(
390                    aggregator.add_child_type_and_descendents::<ValidatorGetRedemptionValueInput>(),
391                ),
392                output: TypeRef::Static(
393                    aggregator
394                        .add_child_type_and_descendents::<ValidatorGetRedemptionValueOutput>(),
395                ),
396                export: VALIDATOR_GET_REDEMPTION_VALUE_IDENT.to_string(),
397            },
398        );
399        functions.insert(
400            VALIDATOR_SIGNAL_PROTOCOL_UPDATE_READINESS_IDENT.to_string(),
401            FunctionSchemaInit {
402                receiver: Some(ReceiverInfo::normal_ref_mut()),
403                input: TypeRef::Static(aggregator
404                    .add_child_type_and_descendents::<ValidatorSignalProtocolUpdateReadinessInput>()),
405                output: TypeRef::Static(aggregator
406                    .add_child_type_and_descendents::<ValidatorSignalProtocolUpdateReadinessOutput>()),
407                export: VALIDATOR_SIGNAL_PROTOCOL_UPDATE_READINESS_IDENT.to_string(),
408            },
409        );
410        functions.insert(
411            VALIDATOR_GET_PROTOCOL_UPDATE_READINESS_IDENT.to_string(),
412            FunctionSchemaInit {
413                receiver: Some(ReceiverInfo::normal_ref_mut()),
414                input: TypeRef::Static(aggregator
415                    .add_child_type_and_descendents::<ValidatorGetProtocolUpdateReadinessInput>()),
416                output: TypeRef::Static(aggregator
417                    .add_child_type_and_descendents::<ValidatorGetProtocolUpdateReadinessOutput>()),
418                export: VALIDATOR_GET_PROTOCOL_UPDATE_READINESS_IDENT.to_string(),
419            },
420        );
421        functions.insert(
422            VALIDATOR_LOCK_OWNER_STAKE_UNITS_IDENT.to_string(),
423            FunctionSchemaInit {
424                receiver: Some(ReceiverInfo::normal_ref_mut()),
425                input: TypeRef::Static(
426                    aggregator
427                        .add_child_type_and_descendents::<ValidatorLockOwnerStakeUnitsInput>(),
428                ),
429                output: TypeRef::Static(
430                    aggregator
431                        .add_child_type_and_descendents::<ValidatorLockOwnerStakeUnitsOutput>(),
432                ),
433                export: VALIDATOR_LOCK_OWNER_STAKE_UNITS_IDENT.to_string(),
434            },
435        );
436        functions.insert(
437            VALIDATOR_START_UNLOCK_OWNER_STAKE_UNITS_IDENT.to_string(),
438            FunctionSchemaInit {
439                receiver: Some(ReceiverInfo::normal_ref_mut()),
440                input: TypeRef::Static(aggregator
441                    .add_child_type_and_descendents::<ValidatorStartUnlockOwnerStakeUnitsInput>()),
442                output: TypeRef::Static(aggregator
443                    .add_child_type_and_descendents::<ValidatorStartUnlockOwnerStakeUnitsOutput>()),
444                export: VALIDATOR_START_UNLOCK_OWNER_STAKE_UNITS_IDENT.to_string(),
445            },
446        );
447        functions.insert(
448            VALIDATOR_FINISH_UNLOCK_OWNER_STAKE_UNITS_IDENT.to_string(),
449            FunctionSchemaInit {
450                receiver: Some(ReceiverInfo::normal_ref_mut()),
451                input: TypeRef::Static(aggregator
452                    .add_child_type_and_descendents::<ValidatorFinishUnlockOwnerStakeUnitsInput>()),
453                output: TypeRef::Static(aggregator
454                    .add_child_type_and_descendents::<ValidatorFinishUnlockOwnerStakeUnitsOutput>()),
455                export: VALIDATOR_FINISH_UNLOCK_OWNER_STAKE_UNITS_IDENT.to_string(),
456            },
457        );
458        functions.insert(
459            VALIDATOR_APPLY_EMISSION_IDENT.to_string(),
460            FunctionSchemaInit {
461                receiver: Some(ReceiverInfo::normal_ref_mut()),
462                input: TypeRef::Static(
463                    aggregator.add_child_type_and_descendents::<ValidatorApplyEmissionInput>(),
464                ),
465                output: TypeRef::Static(
466                    aggregator.add_child_type_and_descendents::<ValidatorApplyEmissionOutput>(),
467                ),
468                export: VALIDATOR_APPLY_EMISSION_IDENT.to_string(),
469            },
470        );
471        functions.insert(
472            VALIDATOR_APPLY_REWARD_IDENT.to_string(),
473            FunctionSchemaInit {
474                receiver: Some(ReceiverInfo::normal_ref_mut()),
475                input: TypeRef::Static(
476                    aggregator.add_child_type_and_descendents::<ValidatorApplyRewardInput>(),
477                ),
478                output: TypeRef::Static(
479                    aggregator.add_child_type_and_descendents::<ValidatorApplyRewardOutput>(),
480                ),
481                export: VALIDATOR_APPLY_REWARD_IDENT.to_string(),
482            },
483        );
484
485        let event_schema = event_schema! {
486            aggregator,
487            [
488                RegisterValidatorEvent,
489                UnregisterValidatorEvent,
490                StakeEvent,
491                UnstakeEvent,
492                ClaimXrdEvent,
493                ProtocolUpdateReadinessSignalEvent,
494                UpdateAcceptingStakeDelegationStateEvent,
495                ValidatorEmissionAppliedEvent,
496                ValidatorRewardAppliedEvent
497            ]
498        };
499
500        let schema = generate_full_schema(aggregator);
501
502        BlueprintDefinitionInit {
503            blueprint_type: BlueprintType::Inner {
504                outer_blueprint: CONSENSUS_MANAGER_BLUEPRINT.to_string(),
505            },
506            is_transient: false,
507            feature_set,
508            dependencies: indexset!(),
509            schema: BlueprintSchemaInit {
510                generics: vec![],
511                schema,
512                state,
513                events: event_schema,
514                types: BlueprintTypeSchemaInit::default(),
515                functions: BlueprintFunctionsSchemaInit { functions },
516                hooks: BlueprintHooksInit::default(),
517            },
518            royalty_config: PackageRoyaltyConfig::default(),
519            auth_config: AuthConfig {
520                function_auth: FunctionAuth::AllowAll,
521                method_auth: MethodAuthTemplate::StaticRoleDefinition(roles_template! {
522                    methods {
523                        VALIDATOR_UNSTAKE_IDENT => MethodAccessibility::Public;
524                        VALIDATOR_CLAIM_XRD_IDENT => MethodAccessibility::Public;
525                        VALIDATOR_STAKE_IDENT => MethodAccessibility::Public;
526                        VALIDATOR_ACCEPTS_DELEGATED_STAKE_IDENT => MethodAccessibility::Public;
527                        VALIDATOR_TOTAL_STAKE_XRD_AMOUNT_IDENT => MethodAccessibility::Public;
528                        VALIDATOR_TOTAL_STAKE_UNIT_SUPPLY_IDENT => MethodAccessibility::Public;
529                        VALIDATOR_GET_REDEMPTION_VALUE_IDENT => MethodAccessibility::Public;
530                        VALIDATOR_STAKE_AS_OWNER_IDENT => [OWNER_ROLE];
531                        VALIDATOR_REGISTER_IDENT => [OWNER_ROLE];
532                        VALIDATOR_UNREGISTER_IDENT => [OWNER_ROLE];
533                        VALIDATOR_UPDATE_KEY_IDENT => [OWNER_ROLE];
534                        VALIDATOR_UPDATE_FEE_IDENT => [OWNER_ROLE];
535                        VALIDATOR_LOCK_OWNER_STAKE_UNITS_IDENT => [OWNER_ROLE];
536                        VALIDATOR_START_UNLOCK_OWNER_STAKE_UNITS_IDENT => [OWNER_ROLE];
537                        VALIDATOR_FINISH_UNLOCK_OWNER_STAKE_UNITS_IDENT => [OWNER_ROLE];
538                        VALIDATOR_UPDATE_ACCEPT_DELEGATED_STAKE_IDENT => [OWNER_ROLE];
539                        VALIDATOR_SIGNAL_PROTOCOL_UPDATE_READINESS_IDENT => [OWNER_ROLE];
540                        VALIDATOR_GET_PROTOCOL_UPDATE_READINESS_IDENT => MethodAccessibility::OuterObjectOnly;
541                        VALIDATOR_APPLY_EMISSION_IDENT => MethodAccessibility::OuterObjectOnly;
542                        VALIDATOR_APPLY_REWARD_IDENT => MethodAccessibility::OuterObjectOnly;
543                    }
544                }),
545            },
546        }
547    }
548
549    pub fn register<Y: SystemApi<RuntimeError>>(api: &mut Y) -> Result<(), RuntimeError> {
550        Self::register_update(true, api)
551    }
552
553    pub fn unregister<Y: SystemApi<RuntimeError>>(api: &mut Y) -> Result<(), RuntimeError> {
554        Self::register_update(false, api)
555    }
556
557    pub fn stake_as_owner<Y: SystemApi<RuntimeError>>(
558        xrd_bucket: Bucket,
559        api: &mut Y,
560    ) -> Result<Bucket, RuntimeError> {
561        Ok(Self::stake_internal(xrd_bucket, true, api)?.into())
562    }
563
564    pub fn stake<Y: SystemApi<RuntimeError>>(
565        xrd_bucket: Bucket,
566        api: &mut Y,
567    ) -> Result<Bucket, RuntimeError> {
568        Ok(Self::stake_internal(xrd_bucket, false, api)?.into())
569    }
570
571    fn stake_internal<Y: SystemApi<RuntimeError>>(
572        xrd_bucket: Bucket,
573        is_owner: bool,
574        api: &mut Y,
575    ) -> Result<FungibleBucket, RuntimeError> {
576        let handle = api.actor_open_field(
577            ACTOR_STATE_SELF,
578            ValidatorField::State.field_index(),
579            LockFlags::MUTABLE,
580        )?;
581
582        let mut validator = api
583            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
584            .fully_update_and_into_latest_version();
585
586        if !is_owner && !validator.accepts_delegated_stake {
587            api.field_close(handle)?;
588
589            // TODO: Should this be an Option returned instead similar to Account?
590            return Err(RuntimeError::ApplicationError(
591                ApplicationError::ValidatorError(
592                    ValidatorError::ValidatorIsNotAcceptingDelegatedStake,
593                ),
594            ));
595        }
596
597        let xrd_bucket_amount = xrd_bucket.amount(api)?;
598
599        // Stake
600        let (stake_unit_bucket, new_stake_amount) = {
601            let mut stake_unit_resman = ResourceManager(validator.stake_unit_resource);
602            let mut xrd_vault = Vault(validator.stake_xrd_vault_id);
603            let stake_unit_mint_amount = Self::calculate_stake_unit_amount(
604                xrd_bucket_amount,
605                xrd_vault.amount(api)?,
606                stake_unit_resman.total_supply(api)?.unwrap(),
607            )?;
608
609            let stake_unit_bucket = stake_unit_resman.mint_fungible(stake_unit_mint_amount, api)?;
610            xrd_vault.put(xrd_bucket, api)?;
611            let new_stake_amount = xrd_vault.amount(api)?;
612            (stake_unit_bucket, new_stake_amount)
613        };
614
615        // Update ConsensusManager
616        let new_index_key =
617            Self::index_update(&validator, validator.is_registered, new_stake_amount, api)?;
618
619        validator.sorted_key = new_index_key;
620        api.field_write_typed(
621            handle,
622            &ValidatorStateFieldPayload::from_content_source(validator),
623        )?;
624
625        Runtime::emit_event(
626            api,
627            StakeEvent {
628                xrd_staked: xrd_bucket_amount,
629            },
630        )?;
631
632        Ok(stake_unit_bucket)
633    }
634
635    pub fn unstake<Y: SystemApi<RuntimeError>>(
636        stake_unit_bucket: Bucket,
637        api: &mut Y,
638    ) -> Result<NonFungibleBucket, RuntimeError> {
639        let stake_unit_bucket_amount = stake_unit_bucket.amount(api)?;
640
641        let handle = api.actor_open_field(
642            ACTOR_STATE_SELF,
643            ValidatorField::State.field_index(),
644            LockFlags::MUTABLE,
645        )?;
646        let mut validator_substate = api
647            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
648            .fully_update_and_into_latest_version();
649
650        // Unstake
651        let (unstake_bucket, new_stake_amount) = {
652            let xrd_amount = Self::calculate_redemption_value(
653                stake_unit_bucket_amount,
654                &validator_substate,
655                api,
656            )?;
657
658            let mut stake_vault = Vault(validator_substate.stake_xrd_vault_id);
659            let mut unstake_vault = Vault(validator_substate.pending_xrd_withdraw_vault_id);
660            let nft_resman = ResourceManager(validator_substate.claim_nft);
661            let mut stake_unit_resman = ResourceManager(validator_substate.stake_unit_resource);
662
663            stake_unit_resman.burn(stake_unit_bucket, api)?;
664
665            let manager_handle = api.actor_open_field(
666                ACTOR_STATE_OUTER_OBJECT,
667                ConsensusManagerField::State.into(),
668                LockFlags::read_only(),
669            )?;
670            let manager_substate = api
671                .field_read_typed::<ConsensusManagerStateFieldPayload>(manager_handle)?
672                .fully_update_and_into_latest_version();
673            let current_epoch = manager_substate.epoch;
674            api.field_close(manager_handle)?;
675
676            let config_handle = api.actor_open_field(
677                ACTOR_STATE_OUTER_OBJECT,
678                ConsensusManagerField::Configuration.into(),
679                LockFlags::read_only(),
680            )?;
681            let config_substate = api
682                .field_read_typed::<ConsensusManagerConfigurationFieldPayload>(config_handle)?
683                .fully_update_and_into_latest_version();
684            api.field_close(config_handle)?;
685
686            let claim_epoch = current_epoch
687                .after(config_substate.config.num_unstake_epochs)
688                .ok_or(RuntimeError::ApplicationError(
689                    ApplicationError::ValidatorError(ValidatorError::EpochMathOverflow),
690                ))?;
691            let data = UnstakeData {
692                name: "Stake Claim".into(),
693                claim_epoch,
694                claim_amount: xrd_amount,
695            };
696
697            let bucket = stake_vault.take(xrd_amount, api)?;
698            unstake_vault.put(bucket, api)?;
699            let (unstake_bucket, _) = nft_resman.mint_non_fungible_single_ruid(data, api)?;
700
701            let new_stake_amount = stake_vault.amount(api)?;
702
703            (unstake_bucket, new_stake_amount)
704        };
705
706        // Update ConsensusManager
707        let new_index_key = Self::index_update(
708            &validator_substate,
709            validator_substate.is_registered,
710            new_stake_amount,
711            api,
712        )?;
713
714        validator_substate.sorted_key = new_index_key;
715        api.field_write_typed(
716            handle,
717            &ValidatorStateFieldPayload::from_content_source(validator_substate),
718        )?;
719
720        Runtime::emit_event(
721            api,
722            UnstakeEvent {
723                stake_units: stake_unit_bucket_amount,
724            },
725        )?;
726
727        Ok(unstake_bucket)
728    }
729
730    pub fn signal_protocol_update_readiness<Y: SystemApi<RuntimeError>>(
731        protocol_version_name: String,
732        api: &mut Y,
733    ) -> Result<(), RuntimeError> {
734        if protocol_version_name.len() != VALIDATOR_PROTOCOL_VERSION_NAME_LEN {
735            return Err(RuntimeError::ApplicationError(
736                ApplicationError::ValidatorError(
737                    ValidatorError::InvalidProtocolVersionNameLength {
738                        expected: VALIDATOR_PROTOCOL_VERSION_NAME_LEN,
739                        actual: protocol_version_name.len(),
740                    },
741                ),
742            ));
743        }
744
745        let handle = api.actor_open_field(
746            ACTOR_STATE_SELF,
747            ValidatorField::ProtocolUpdateReadinessSignal.into(),
748            LockFlags::MUTABLE,
749        )?;
750        let mut signal = api
751            .field_read_typed::<ValidatorProtocolUpdateReadinessSignalFieldPayload>(handle)?
752            .fully_update_and_into_latest_version();
753        signal.protocol_version_name = Some(protocol_version_name.clone());
754        api.field_write_typed(
755            handle,
756            &ValidatorProtocolUpdateReadinessSignalFieldPayload::from_content_source(signal),
757        )?;
758        api.field_close(handle)?;
759
760        Runtime::emit_event(
761            api,
762            ProtocolUpdateReadinessSignalEvent {
763                protocol_version_name,
764            },
765        )?;
766
767        Ok(())
768    }
769
770    pub fn get_protocol_update_readiness<Y: SystemApi<RuntimeError>>(
771        api: &mut Y,
772    ) -> Result<Option<String>, RuntimeError> {
773        let handle = api.actor_open_field(
774            ACTOR_STATE_SELF,
775            ValidatorField::ProtocolUpdateReadinessSignal.into(),
776            LockFlags::read_only(),
777        )?;
778        let signal = api
779            .field_read_typed::<ValidatorProtocolUpdateReadinessSignalFieldPayload>(handle)?
780            .fully_update_and_into_latest_version();
781        api.field_close(handle)?;
782
783        Ok(signal.protocol_version_name)
784    }
785
786    fn register_update<Y: SystemApi<RuntimeError>>(
787        new_registered: bool,
788        api: &mut Y,
789    ) -> Result<(), RuntimeError> {
790        let handle = api.actor_open_field(
791            ACTOR_STATE_SELF,
792            ValidatorField::State.field_index(),
793            LockFlags::MUTABLE,
794        )?;
795
796        let mut validator = api
797            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
798            .fully_update_and_into_latest_version();
799        // No update
800        if validator.is_registered == new_registered {
801            return Ok(());
802        }
803
804        let stake_amount = {
805            let stake_vault = Vault(validator.stake_xrd_vault_id);
806            stake_vault.amount(api)?
807        };
808
809        let index_key = Self::index_update(&validator, new_registered, stake_amount, api)?;
810
811        validator.is_registered = new_registered;
812        validator.sorted_key = index_key;
813        api.field_write_typed(
814            handle,
815            &ValidatorStateFieldPayload::from_content_source(validator),
816        )?;
817
818        if new_registered {
819            Runtime::emit_event(api, RegisterValidatorEvent)?;
820        } else {
821            Runtime::emit_event(api, UnregisterValidatorEvent)?;
822        }
823
824        Ok(())
825    }
826
827    fn index_update<Y: SystemApi<RuntimeError>>(
828        validator: &ValidatorSubstate,
829        new_registered: bool,
830        new_stake_amount: Decimal,
831        api: &mut Y,
832    ) -> Result<Option<SortedKey>, RuntimeError> {
833        let validator_address: ComponentAddress =
834            ComponentAddress::new_or_panic(api.actor_get_node_id(ACTOR_REF_GLOBAL)?.into());
835        let new_sorted_key =
836            Self::to_sorted_key(new_registered, new_stake_amount, validator_address)?;
837
838        let update = if let Some(cur_index_key) = &validator.sorted_key {
839            if let Some(new_index_key) = &new_sorted_key {
840                Some(UpdateSecondaryIndex::UpdateStake {
841                    index_key: cur_index_key.clone(),
842                    new_index_key: new_index_key.clone(),
843                    new_stake_amount,
844                })
845            } else {
846                Some(UpdateSecondaryIndex::Remove {
847                    index_key: cur_index_key.clone(),
848                })
849            }
850        } else {
851            new_sorted_key
852                .as_ref()
853                .map(|new_index_key| UpdateSecondaryIndex::Create {
854                    index_key: new_index_key.clone(),
855                    stake: new_stake_amount,
856                    key: validator.key,
857                })
858        };
859
860        if let Some(update) = update {
861            Self::update_validator(update, api)?;
862        }
863
864        Ok(new_sorted_key)
865    }
866
867    pub fn claim_xrd<Y: SystemApi<RuntimeError>>(
868        bucket: Bucket,
869        api: &mut Y,
870    ) -> Result<Bucket, RuntimeError> {
871        let handle = api.actor_open_field(
872            ACTOR_STATE_SELF,
873            ValidatorField::State.field_index(),
874            LockFlags::read_only(),
875        )?;
876        let validator = api
877            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
878            .fully_update_and_into_latest_version();
879        let mut nft_resman = ResourceManager(validator.claim_nft);
880        let resource_address = validator.claim_nft;
881        let mut unstake_vault = Vault(validator.pending_xrd_withdraw_vault_id);
882
883        if !resource_address.eq(&bucket.resource_address(api)?) {
884            return Err(RuntimeError::ApplicationError(
885                ApplicationError::ValidatorError(ValidatorError::InvalidClaimResource),
886            ));
887        }
888
889        let current_epoch = {
890            let mgr_handle = api.actor_open_field(
891                ACTOR_STATE_OUTER_OBJECT,
892                ConsensusManagerField::State.field_index(),
893                LockFlags::read_only(),
894            )?;
895            let mgr_substate = api
896                .field_read_typed::<ConsensusManagerStateFieldPayload>(mgr_handle)?
897                .fully_update_and_into_latest_version();
898            let epoch = mgr_substate.epoch;
899            api.field_close(mgr_handle)?;
900            epoch
901        };
902
903        let mut unstake_amount = Decimal::zero();
904
905        for id in bucket.non_fungible_local_ids(api)? {
906            let data: UnstakeData = nft_resman.get_non_fungible_data(id, api)?;
907            if current_epoch < data.claim_epoch {
908                return Err(RuntimeError::ApplicationError(
909                    ApplicationError::ValidatorError(ValidatorError::EpochUnlockHasNotOccurredYet),
910                ));
911            }
912            unstake_amount = unstake_amount.checked_add(data.claim_amount).ok_or(
913                RuntimeError::ApplicationError(ApplicationError::ValidatorError(
914                    ValidatorError::UnexpectedDecimalComputationError,
915                )),
916            )?;
917        }
918        nft_resman.burn(bucket, api)?;
919
920        let claimed_bucket = unstake_vault.take(unstake_amount, api)?;
921
922        let amount = claimed_bucket.amount(api)?;
923        Runtime::emit_event(
924            api,
925            ClaimXrdEvent {
926                claimed_xrd: amount,
927            },
928        )?;
929
930        Ok(claimed_bucket)
931    }
932
933    pub fn update_key<Y: SystemApi<RuntimeError>>(
934        key: Secp256k1PublicKey,
935        api: &mut Y,
936    ) -> Result<(), RuntimeError> {
937        let handle = api.actor_open_field(
938            ACTOR_STATE_SELF,
939            ValidatorField::State.into(),
940            LockFlags::MUTABLE,
941        )?;
942        let mut validator = api
943            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
944            .fully_update_and_into_latest_version();
945
946        // Update Consensus Manager
947        {
948            if let Some(index_key) = &validator.sorted_key {
949                let update = UpdateSecondaryIndex::UpdatePublicKey {
950                    index_key: index_key.clone(),
951                    key,
952                };
953
954                Self::update_validator(update, api)?;
955            }
956        }
957
958        validator.key = key;
959        api.field_write_typed(
960            handle,
961            &ValidatorStateFieldPayload::from_content_source(validator),
962        )?;
963
964        Ok(())
965    }
966
967    pub fn update_fee<Y: SystemApi<RuntimeError>>(
968        new_fee_factor: Decimal,
969        api: &mut Y,
970    ) -> Result<(), RuntimeError> {
971        // check if new fee is valid
972        check_validator_fee_factor(new_fee_factor)?;
973
974        // read the current epoch
975        let consensus_manager_handle = api.actor_open_field(
976            ACTOR_STATE_OUTER_OBJECT,
977            ConsensusManagerField::State.into(),
978            LockFlags::read_only(),
979        )?;
980        let consensus_manager = api
981            .field_read_typed::<ConsensusManagerStateFieldPayload>(consensus_manager_handle)?
982            .fully_update_and_into_latest_version();
983        let current_epoch = consensus_manager.epoch;
984        api.field_close(consensus_manager_handle)?;
985
986        // read the configured fee increase epochs delay
987        let config_handle = api.actor_open_field(
988            ACTOR_STATE_OUTER_OBJECT,
989            ConsensusManagerField::Configuration.into(),
990            LockFlags::read_only(),
991        )?;
992        let config_substate = api
993            .field_read_typed::<ConsensusManagerConfigurationFieldPayload>(config_handle)?
994            .fully_update_and_into_latest_version();
995        api.field_close(config_handle)?;
996
997        // begin the read+modify+write of the validator substate...
998        let handle = api.actor_open_field(
999            ACTOR_STATE_SELF,
1000            ValidatorField::State.into(),
1001            LockFlags::MUTABLE,
1002        )?;
1003        let mut substate = api
1004            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
1005            .fully_update_and_into_latest_version();
1006
1007        // - promote any currently pending change if it became effective already
1008        if let Some(previous_request) = substate.validator_fee_change_request {
1009            if previous_request.epoch_effective <= current_epoch {
1010                substate.validator_fee_factor = previous_request.new_fee_factor;
1011            }
1012        }
1013
1014        // - calculate the effective epoch of the requested change
1015        let epoch_effective = if new_fee_factor > substate.validator_fee_factor {
1016            current_epoch.after(config_substate.config.num_fee_increase_delay_epochs)
1017        } else {
1018            current_epoch.next() // make it effective on the *beginning* of next epoch
1019        }
1020        .ok_or(RuntimeError::ApplicationError(
1021            ApplicationError::ValidatorError(ValidatorError::EpochMathOverflow),
1022        ))?;
1023
1024        // ...end the read+modify+write of the validator substate
1025        substate.validator_fee_change_request = Some(ValidatorFeeChangeRequest {
1026            epoch_effective,
1027            new_fee_factor,
1028        });
1029        api.field_write_typed(
1030            handle,
1031            &ValidatorStateFieldPayload::from_content_source(substate),
1032        )?;
1033        api.field_close(handle)?;
1034
1035        Ok(())
1036    }
1037
1038    pub fn accepts_delegated_stake<Y: SystemApi<RuntimeError>>(
1039        api: &mut Y,
1040    ) -> Result<bool, RuntimeError> {
1041        let handle = api.actor_open_field(
1042            ACTOR_STATE_SELF,
1043            ValidatorField::State.into(),
1044            LockFlags::read_only(),
1045        )?;
1046
1047        let substate = api
1048            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
1049            .fully_update_and_into_latest_version();
1050        api.field_close(handle)?;
1051
1052        Ok(substate.accepts_delegated_stake)
1053    }
1054
1055    pub fn total_stake_xrd_amount<Y: SystemApi<RuntimeError>>(
1056        api: &mut Y,
1057    ) -> Result<Decimal, RuntimeError> {
1058        let handle = api.actor_open_field(
1059            ACTOR_STATE_SELF,
1060            ValidatorField::State.into(),
1061            LockFlags::read_only(),
1062        )?;
1063
1064        let substate = api
1065            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
1066            .fully_update_and_into_latest_version();
1067        let stake_vault = Vault(substate.stake_xrd_vault_id);
1068        let stake_amount = stake_vault.amount(api)?;
1069        api.field_close(handle)?;
1070
1071        Ok(stake_amount)
1072    }
1073
1074    pub fn total_stake_unit_supply<Y: SystemApi<RuntimeError>>(
1075        api: &mut Y,
1076    ) -> Result<Decimal, RuntimeError> {
1077        let handle = api.actor_open_field(
1078            ACTOR_STATE_SELF,
1079            ValidatorField::State.into(),
1080            LockFlags::read_only(),
1081        )?;
1082
1083        let substate = api
1084            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
1085            .fully_update_and_into_latest_version();
1086        let stake_resource = ResourceManager(substate.stake_unit_resource);
1087        let total_stake_unit_supply = stake_resource.total_supply(api)?.unwrap();
1088        api.field_close(handle)?;
1089
1090        Ok(total_stake_unit_supply)
1091    }
1092
1093    pub fn get_redemption_value<Y: SystemApi<RuntimeError>>(
1094        amount_of_stake_units: Decimal,
1095        api: &mut Y,
1096    ) -> Result<Decimal, RuntimeError> {
1097        if amount_of_stake_units.is_negative() || amount_of_stake_units.is_zero() {
1098            return Err(RuntimeError::ApplicationError(
1099                ApplicationError::ValidatorError(ValidatorError::InvalidGetRedemptionAmount),
1100            ));
1101        }
1102
1103        let handle = api.actor_open_field(
1104            ACTOR_STATE_SELF,
1105            ValidatorField::State.into(),
1106            LockFlags::read_only(),
1107        )?;
1108        let validator = api
1109            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
1110            .fully_update_and_into_latest_version();
1111
1112        {
1113            let stake_unit_resman = ResourceManager(validator.stake_unit_resource);
1114            let total_stake_unit_supply = stake_unit_resman.total_supply(api)?.unwrap();
1115            if amount_of_stake_units > total_stake_unit_supply {
1116                return Err(RuntimeError::ApplicationError(
1117                    ApplicationError::ValidatorError(ValidatorError::InvalidGetRedemptionAmount),
1118                ));
1119            }
1120        }
1121
1122        let redemption_value =
1123            Self::calculate_redemption_value(amount_of_stake_units, &validator, api)?;
1124        api.field_close(handle)?;
1125
1126        Ok(redemption_value)
1127    }
1128
1129    pub fn update_accept_delegated_stake<Y: SystemApi<RuntimeError>>(
1130        accept_delegated_stake: bool,
1131        api: &mut Y,
1132    ) -> Result<(), RuntimeError> {
1133        let handle = api.actor_open_field(
1134            ACTOR_STATE_SELF,
1135            ValidatorField::State.into(),
1136            LockFlags::MUTABLE,
1137        )?;
1138        let mut substate = api
1139            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
1140            .fully_update_and_into_latest_version();
1141        substate.accepts_delegated_stake = accept_delegated_stake;
1142        api.field_write_typed(
1143            handle,
1144            &ValidatorStateFieldPayload::from_content_source(substate),
1145        )?;
1146        api.field_close(handle)?;
1147
1148        Runtime::emit_event(
1149            api,
1150            UpdateAcceptingStakeDelegationStateEvent {
1151                accepts_delegation: accept_delegated_stake,
1152            },
1153        )?;
1154
1155        Ok(())
1156    }
1157
1158    /// Locks the given stake units in an internal "delayed withdrawal" vault (which is the owner's
1159    /// way of showing their commitment to running this validator in an orderly fashion - see
1160    /// [`ValidatorSubstate.locked_owner_stake_unit_vault_id`]).
1161    pub fn lock_owner_stake_units<Y: SystemApi<RuntimeError>>(
1162        stake_unit_bucket: Bucket,
1163        api: &mut Y,
1164    ) -> Result<(), RuntimeError> {
1165        let handle = api.actor_open_field(
1166            ACTOR_STATE_SELF,
1167            ValidatorField::State.into(),
1168            LockFlags::read_only(),
1169        )?;
1170        let substate = api
1171            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
1172            .fully_update_and_into_latest_version();
1173
1174        Vault(substate.locked_owner_stake_unit_vault_id).put(stake_unit_bucket, api)?;
1175
1176        api.field_close(handle)?;
1177        Ok(())
1178    }
1179
1180    /// Starts the process of unlocking the owner's stake units stored in the internal vault.
1181    /// The requested amount of stake units (if available) will be ready for withdrawal after the
1182    /// network-configured [`ConsensusManagerConfigSubstate.num_owner_stake_units_unlock_epochs`] via a
1183    /// call to [`finish_unlock_owner_stake_units()`].
1184    pub fn start_unlock_owner_stake_units<Y: SystemApi<RuntimeError>>(
1185        requested_stake_unit_amount: Decimal,
1186        api: &mut Y,
1187    ) -> Result<(), RuntimeError> {
1188        // read the current epoch (needed for a drive-by "finish unlocking" of available withdrawals)
1189        let consensus_manager_handle = api.actor_open_field(
1190            ACTOR_STATE_OUTER_OBJECT,
1191            ConsensusManagerField::State.into(),
1192            LockFlags::read_only(),
1193        )?;
1194        let consensus_manager = api
1195            .field_read_typed::<ConsensusManagerStateFieldPayload>(consensus_manager_handle)?
1196            .fully_update_and_into_latest_version();
1197        let current_epoch = consensus_manager.epoch;
1198        api.field_close(consensus_manager_handle)?;
1199
1200        // read the configured unlock epochs delay
1201        let config_handle = api.actor_open_field(
1202            ACTOR_STATE_OUTER_OBJECT,
1203            ConsensusManagerField::Configuration.into(),
1204            LockFlags::read_only(),
1205        )?;
1206        let config_substate = api
1207            .field_read_typed::<ConsensusManagerConfigurationFieldPayload>(config_handle)?
1208            .fully_update_and_into_latest_version();
1209        api.field_close(config_handle)?;
1210
1211        // begin the read+modify+write of the validator substate...
1212        let handle = api.actor_open_field(
1213            ACTOR_STATE_SELF,
1214            ValidatorField::State.into(),
1215            LockFlags::MUTABLE,
1216        )?;
1217        let mut substate = api
1218            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
1219            .fully_update_and_into_latest_version();
1220
1221        // - move the already-available withdrawals to a dedicated field
1222        Self::normalize_available_owner_stake_unit_withdrawals(&mut substate, current_epoch)?;
1223
1224        // - insert the requested withdrawal as pending
1225        substate
1226            .pending_owner_stake_unit_withdrawals
1227            .entry(
1228                current_epoch
1229                    .after(config_substate.config.num_owner_stake_units_unlock_epochs)
1230                    .ok_or(RuntimeError::ApplicationError(
1231                        ApplicationError::ValidatorError(ValidatorError::EpochMathOverflow),
1232                    ))?,
1233            )
1234            .and_modify(|pending_amount| {
1235                *pending_amount = pending_amount
1236                    .checked_add(requested_stake_unit_amount)
1237                    .unwrap_or(Decimal::MAX)
1238            })
1239            .or_insert(requested_stake_unit_amount);
1240
1241        // ...end the read+modify+write of the validator substate
1242        let mut locked_owner_stake_unit_vault = Vault(substate.locked_owner_stake_unit_vault_id);
1243        let mut pending_owner_stake_unit_unlock_vault =
1244            Vault(substate.pending_owner_stake_unit_unlock_vault_id);
1245        api.field_write_typed(
1246            handle,
1247            &ValidatorStateFieldPayload::from_content_source(substate),
1248        )?;
1249
1250        // move the requested stake units from the "locked vault" to the "pending withdrawal vault"
1251        let pending_unlock_stake_unit_bucket =
1252            locked_owner_stake_unit_vault.take(requested_stake_unit_amount, api)?;
1253        pending_owner_stake_unit_unlock_vault.put(pending_unlock_stake_unit_bucket, api)?;
1254
1255        api.field_close(handle)?;
1256        Ok(())
1257    }
1258
1259    /// Finishes the process of unlocking the owner's stake units by withdrawing *all* the pending
1260    /// amounts which have reached their target epoch and thus are already available (potentially
1261    /// none).
1262    pub fn finish_unlock_owner_stake_units<Y: SystemApi<RuntimeError>>(
1263        api: &mut Y,
1264    ) -> Result<Bucket, RuntimeError> {
1265        // read the current epoch
1266        let consensus_manager_handle = api.actor_open_field(
1267            ACTOR_STATE_OUTER_OBJECT,
1268            ConsensusManagerField::State.into(),
1269            LockFlags::read_only(),
1270        )?;
1271        let consensus_manager = api
1272            .field_read_typed::<ConsensusManagerStateFieldPayload>(consensus_manager_handle)?
1273            .fully_update_and_into_latest_version();
1274        let current_epoch = consensus_manager.epoch;
1275        api.field_close(consensus_manager_handle)?;
1276
1277        // drain the already-available withdrawals
1278        let handle = api.actor_open_field(
1279            ACTOR_STATE_SELF,
1280            ValidatorField::State.into(),
1281            LockFlags::MUTABLE,
1282        )?;
1283        let mut substate = api
1284            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
1285            .fully_update_and_into_latest_version();
1286
1287        Self::normalize_available_owner_stake_unit_withdrawals(&mut substate, current_epoch)?;
1288        let total_already_available_amount = mem::replace(
1289            &mut substate.already_unlocked_owner_stake_unit_amount,
1290            Decimal::zero(),
1291        );
1292
1293        let mut pending_owner_stake_unit_unlock_vault =
1294            Vault(substate.pending_owner_stake_unit_unlock_vault_id);
1295        api.field_write_typed(
1296            handle,
1297            &ValidatorStateFieldPayload::from_content_source(substate),
1298        )?;
1299
1300        // return the already-available withdrawals
1301        let already_available_stake_unit_bucket =
1302            pending_owner_stake_unit_unlock_vault.take(total_already_available_amount, api)?;
1303
1304        api.field_close(handle)?;
1305        Ok(already_available_stake_unit_bucket)
1306    }
1307
1308    /// Removes all no-longer-pending owner stake unit withdrawals (i.e. those which have already
1309    /// reached the given [`current_epoch`]) from [`pending_owner_stake_unit_withdrawals`] into
1310    /// [`already_unlocked_owner_stake_unit_amount`].
1311    /// Note: this house-keeping operation prevents the internal collection from growing to a size
1312    /// which would affect performance (or exceed the substate size limit).
1313    fn normalize_available_owner_stake_unit_withdrawals(
1314        substate: &mut ValidatorSubstate,
1315        current_epoch: Epoch,
1316    ) -> Result<(), RuntimeError> {
1317        let available_withdrawal_epochs = substate
1318            .pending_owner_stake_unit_withdrawals
1319            .range(..=current_epoch)
1320            .map(|(epoch, _available_amount)| *epoch)
1321            .collect::<Vec<_>>();
1322        for available_withdrawal_epoch in available_withdrawal_epochs {
1323            // no batch delete in a BTree
1324            let available_amount = substate
1325                .pending_owner_stake_unit_withdrawals
1326                .remove(&available_withdrawal_epoch)
1327                .expect("key was just returned by the iterator");
1328            substate.already_unlocked_owner_stake_unit_amount = substate
1329                .already_unlocked_owner_stake_unit_amount
1330                .checked_add(available_amount)
1331                .ok_or(RuntimeError::ApplicationError(
1332                    ApplicationError::ValidatorError(
1333                        ValidatorError::UnexpectedDecimalComputationError,
1334                    ),
1335                ))?;
1336        }
1337        Ok(())
1338    }
1339
1340    /// Puts the given bucket into this validator's stake XRD vault, effectively increasing the
1341    /// value of all its stake units.
1342    /// Note: the validator's proposal statistics passed to this method are used only for creating
1343    /// an event (i.e. they are only informational and they do not drive any logic at this point).
1344    pub fn apply_emission<Y: SystemApi<RuntimeError>>(
1345        xrd_bucket: Bucket,
1346        concluded_epoch: Epoch,
1347        proposals_made: u64,
1348        proposals_missed: u64,
1349        api: &mut Y,
1350    ) -> Result<(), RuntimeError> {
1351        // begin the read+modify+write of the validator substate...
1352        let handle = api.actor_open_field(
1353            ACTOR_STATE_SELF,
1354            ValidatorField::State.into(),
1355            LockFlags::MUTABLE,
1356        )?;
1357        let mut substate = api
1358            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
1359            .fully_update_and_into_latest_version();
1360
1361        // - resolve the effective validator fee factor
1362        let effective_validator_fee_factor = match &substate.validator_fee_change_request {
1363            Some(request) if request.epoch_effective <= concluded_epoch => request.new_fee_factor,
1364            _ => substate.validator_fee_factor,
1365        };
1366
1367        // - calculate the validator fee and subtract it from the emission bucket
1368        let total_emission_xrd = xrd_bucket.amount(api)?;
1369        let validator_fee_xrd = effective_validator_fee_factor
1370            .checked_mul(total_emission_xrd)
1371            .ok_or(RuntimeError::ApplicationError(
1372                ApplicationError::ValidatorError(ValidatorError::UnexpectedDecimalComputationError),
1373            ))?;
1374        let fee_xrd_bucket = xrd_bucket.take(validator_fee_xrd, api)?;
1375
1376        // - put the net emission XRDs into the stake pool
1377        let mut stake_xrd_vault = Vault(substate.stake_xrd_vault_id);
1378        let starting_stake_pool_xrd = stake_xrd_vault.amount(api)?;
1379        stake_xrd_vault.put(xrd_bucket, api)?;
1380
1381        // - stake the validator fee XRDs (effectively same as regular staking)
1382        let mut stake_unit_resman = ResourceManager(substate.stake_unit_resource);
1383        let stake_pool_added_xrd = total_emission_xrd.checked_sub(validator_fee_xrd).ok_or(
1384            RuntimeError::ApplicationError(ApplicationError::ValidatorError(
1385                ValidatorError::UnexpectedDecimalComputationError,
1386            )),
1387        )?;
1388        let post_emission_stake_pool_xrd = starting_stake_pool_xrd
1389            .checked_add(stake_pool_added_xrd)
1390            .ok_or(RuntimeError::ApplicationError(
1391                ApplicationError::ValidatorError(ValidatorError::UnexpectedDecimalComputationError),
1392            ))?;
1393        let total_stake_unit_supply = stake_unit_resman.total_supply(api)?.unwrap();
1394        let stake_unit_mint_amount = Self::calculate_stake_unit_amount(
1395            validator_fee_xrd,
1396            post_emission_stake_pool_xrd,
1397            total_stake_unit_supply,
1398        )?;
1399        let fee_stake_unit_bucket = stake_unit_resman.mint_fungible(stake_unit_mint_amount, api)?;
1400        stake_xrd_vault.put(fee_xrd_bucket, api)?;
1401
1402        // - immediately lock these new stake units in the internal owner's "public display" vault
1403        Vault(substate.locked_owner_stake_unit_vault_id).put(fee_stake_unit_bucket.into(), api)?;
1404
1405        // - update the index, since the stake increased (because of net emission + staking of the validator fee)
1406        let new_stake_xrd = starting_stake_pool_xrd
1407            .checked_add(total_emission_xrd)
1408            .ok_or(RuntimeError::ApplicationError(
1409                ApplicationError::ValidatorError(ValidatorError::UnexpectedDecimalComputationError),
1410            ))?;
1411        let new_index_key =
1412            Self::index_update(&substate, substate.is_registered, new_stake_xrd, api)?;
1413
1414        // ...end the read+modify+write of the validator substate (event can be emitted afterwards)
1415        substate.sorted_key = new_index_key;
1416        api.field_write_typed(
1417            handle,
1418            &ValidatorStateFieldPayload::from_content_source(substate),
1419        )?;
1420        api.field_close(handle)?;
1421
1422        Runtime::emit_event(
1423            api,
1424            ValidatorEmissionAppliedEvent {
1425                epoch: concluded_epoch,
1426                starting_stake_pool_xrd,
1427                stake_pool_added_xrd,
1428                total_stake_unit_supply,
1429                validator_fee_xrd,
1430                proposals_made,
1431                proposals_missed,
1432            },
1433        )?;
1434
1435        Ok(())
1436    }
1437
1438    pub fn apply_reward<Y: SystemApi<RuntimeError>>(
1439        xrd_bucket: Bucket,
1440        concluded_epoch: Epoch,
1441        api: &mut Y,
1442    ) -> Result<(), RuntimeError> {
1443        // begin the read+modify+write of the validator substate...
1444        let handle = api.actor_open_field(
1445            ACTOR_STATE_SELF,
1446            ValidatorField::State.into(),
1447            LockFlags::MUTABLE,
1448        )?;
1449        let mut substate = api
1450            .field_read_typed::<ValidatorStateFieldPayload>(handle)?
1451            .fully_update_and_into_latest_version();
1452
1453        // Get the total reward amount
1454        let total_reward_xrd = xrd_bucket.amount(api)?;
1455
1456        // Stake it
1457        let mut stake_xrd_vault = Vault(substate.stake_xrd_vault_id);
1458        let starting_stake_pool_xrd = stake_xrd_vault.amount(api)?;
1459        let mut stake_unit_resman = ResourceManager(substate.stake_unit_resource);
1460        let total_stake_unit_supply = stake_unit_resman.total_supply(api)?.unwrap();
1461        let stake_unit_mint_amount = Self::calculate_stake_unit_amount(
1462            total_reward_xrd,
1463            starting_stake_pool_xrd,
1464            total_stake_unit_supply,
1465        )?;
1466        let new_stake_unit_bucket = stake_unit_resman.mint_fungible(stake_unit_mint_amount, api)?;
1467        stake_xrd_vault.put(xrd_bucket, api)?;
1468
1469        // Lock these new stake units in the internal owner's "public display" vault
1470        Vault(substate.locked_owner_stake_unit_vault_id).put(new_stake_unit_bucket.into(), api)?;
1471
1472        // Update the index, since the stake increased (because of staking of the reward)
1473        let new_stake_xrd = starting_stake_pool_xrd
1474            .checked_add(total_reward_xrd)
1475            .ok_or(RuntimeError::ApplicationError(
1476                ApplicationError::ValidatorError(ValidatorError::UnexpectedDecimalComputationError),
1477            ))?;
1478        let new_index_key =
1479            Self::index_update(&substate, substate.is_registered, new_stake_xrd, api)?;
1480
1481        // Flush validator substate changes
1482        substate.sorted_key = new_index_key;
1483        api.field_write_typed(
1484            handle,
1485            &ValidatorStateFieldPayload::from_content_source(substate),
1486        )?;
1487        api.field_close(handle)?;
1488
1489        Runtime::emit_event(
1490            api,
1491            ValidatorRewardAppliedEvent {
1492                epoch: concluded_epoch,
1493                amount: total_reward_xrd,
1494            },
1495        )?;
1496
1497        Ok(())
1498    }
1499
1500    fn to_sorted_key(
1501        registered: bool,
1502        stake: Decimal,
1503        address: ComponentAddress,
1504    ) -> Result<Option<SortedKey>, RuntimeError> {
1505        if !registered || stake.is_zero() {
1506            Ok(None)
1507        } else {
1508            Ok(Some((
1509                create_sort_prefix_from_stake(stake)?,
1510                scrypto_encode(&address).unwrap(),
1511            )))
1512        }
1513    }
1514
1515    fn update_validator<Y: SystemApi<RuntimeError>>(
1516        update: UpdateSecondaryIndex,
1517        api: &mut Y,
1518    ) -> Result<(), RuntimeError> {
1519        match update {
1520            UpdateSecondaryIndex::Create {
1521                index_key,
1522                key,
1523                stake,
1524            } => {
1525                api.actor_sorted_index_insert_typed(
1526                    ACTOR_STATE_OUTER_OBJECT,
1527                    ConsensusManagerCollection::RegisteredValidatorByStakeSortedIndex
1528                        .collection_index(),
1529                    index_key,
1530                    ConsensusManagerRegisteredValidatorByStakeEntryPayload::from_content_source(
1531                        Validator { key, stake },
1532                    ),
1533                )?;
1534            }
1535            UpdateSecondaryIndex::UpdatePublicKey { index_key, key } => {
1536                let mut validator = api
1537                    .actor_sorted_index_remove_typed::<ConsensusManagerRegisteredValidatorByStakeEntryPayload>(
1538                        ACTOR_STATE_OUTER_OBJECT,
1539                        ConsensusManagerCollection::RegisteredValidatorByStakeSortedIndex.collection_index(),
1540                        &index_key,
1541                    )?
1542                    .unwrap().fully_update_and_into_latest_version();
1543                validator.key = key;
1544                api.actor_sorted_index_insert_typed(
1545                    ACTOR_STATE_OUTER_OBJECT,
1546                    ConsensusManagerCollection::RegisteredValidatorByStakeSortedIndex
1547                        .collection_index(),
1548                    index_key,
1549                    ConsensusManagerRegisteredValidatorByStakeEntryPayload::from_content_source(
1550                        validator,
1551                    ),
1552                )?;
1553            }
1554            UpdateSecondaryIndex::UpdateStake {
1555                index_key,
1556                new_index_key,
1557                new_stake_amount,
1558            } => {
1559                let mut validator = api
1560                    .actor_sorted_index_remove_typed::<ConsensusManagerRegisteredValidatorByStakeEntryPayload>(
1561                        ACTOR_STATE_OUTER_OBJECT,
1562                        ConsensusManagerCollection::RegisteredValidatorByStakeSortedIndex.collection_index(),
1563                        &index_key,
1564                    )?
1565                    .unwrap().fully_update_and_into_latest_version();
1566                validator.stake = new_stake_amount;
1567                api.actor_sorted_index_insert_typed(
1568                    ACTOR_STATE_OUTER_OBJECT,
1569                    ConsensusManagerCollection::RegisteredValidatorByStakeSortedIndex
1570                        .collection_index(),
1571                    new_index_key,
1572                    ConsensusManagerRegisteredValidatorByStakeEntryPayload::from_content_source(
1573                        validator,
1574                    ),
1575                )?;
1576            }
1577            UpdateSecondaryIndex::Remove { index_key } => {
1578                api.actor_sorted_index_remove(
1579                    ACTOR_STATE_OUTER_OBJECT,
1580                    ConsensusManagerCollection::RegisteredValidatorByStakeSortedIndex
1581                        .collection_index(),
1582                    &index_key,
1583                )?;
1584            }
1585        }
1586
1587        Ok(())
1588    }
1589
1590    fn calculate_redemption_value<Y: SystemApi<RuntimeError>>(
1591        amount_of_stake_units: Decimal,
1592        validator_substate: &ValidatorSubstate,
1593        api: &mut Y,
1594    ) -> Result<Decimal, RuntimeError> {
1595        let stake_vault = Vault(validator_substate.stake_xrd_vault_id);
1596        let stake_unit_resman = ResourceManager(validator_substate.stake_unit_resource);
1597
1598        let active_stake_amount = stake_vault.amount(api)?;
1599        let total_stake_unit_supply = stake_unit_resman.total_supply(api)?.unwrap();
1600        let xrd_amount = if total_stake_unit_supply.is_zero() {
1601            Decimal::zero()
1602        } else {
1603            active_stake_amount
1604                .checked_div(total_stake_unit_supply)
1605                .and_then(|amount| amount_of_stake_units.checked_mul(amount))
1606                .ok_or(RuntimeError::ApplicationError(
1607                    ApplicationError::ValidatorError(
1608                        ValidatorError::UnexpectedDecimalComputationError,
1609                    ),
1610                ))?
1611        };
1612
1613        Ok(xrd_amount)
1614    }
1615
1616    /// Returns an amount of stake units to be minted when [`xrd_amount`] of XRDs is being staked.
1617    fn calculate_stake_unit_amount(
1618        xrd_amount: Decimal,
1619        total_stake_xrd_amount: Decimal,
1620        total_stake_unit_supply: Decimal,
1621    ) -> Result<Decimal, RuntimeError> {
1622        if total_stake_xrd_amount.is_zero() {
1623            Ok(xrd_amount)
1624        } else {
1625            total_stake_unit_supply
1626                .checked_div(total_stake_xrd_amount)
1627                .and_then(|amount| xrd_amount.checked_mul(amount))
1628                .ok_or(RuntimeError::ApplicationError(
1629                    ApplicationError::ValidatorError(
1630                        ValidatorError::UnexpectedDecimalComputationError,
1631                    ),
1632                ))
1633        }
1634    }
1635}
1636
1637fn check_validator_fee_factor(fee_factor: Decimal) -> Result<(), RuntimeError> {
1638    // only allow a proper fraction
1639    if fee_factor.is_negative() || fee_factor > Decimal::one() {
1640        return Err(RuntimeError::ApplicationError(
1641            ApplicationError::ValidatorError(ValidatorError::InvalidValidatorFeeFactor),
1642        ));
1643    }
1644    Ok(())
1645}
1646
1647fn create_sort_prefix_from_stake(stake: Decimal) -> Result<[u8; 2], RuntimeError> {
1648    // Note: XRD max supply is 24bn
1649    // 24bn / MAX::16 = 366210.9375 - so 100k as a divisor here is sensible.
1650    // If all available XRD was staked to one validator, they'd have 3.6 * u16::MAX * 100k stake
1651    // In reality, validators will have far less than u16::MAX * 100k stake, but let's handle that case just in case
1652    let stake_100k: Decimal = stake
1653        .checked_div(100000)
1654        .ok_or(RuntimeError::ApplicationError(
1655            ApplicationError::ValidatorError(ValidatorError::UnexpectedDecimalComputationError),
1656        ))?;
1657
1658    let stake_100k_whole_units = dec!(10)
1659        .checked_powi(Decimal::SCALE.into())
1660        .and_then(|power| stake_100k.checked_div(power))
1661        .ok_or(RuntimeError::ApplicationError(
1662            ApplicationError::ValidatorError(ValidatorError::UnexpectedDecimalComputationError),
1663        ))?
1664        .attos();
1665
1666    let stake_u16 = if stake_100k_whole_units > I192::from(u16::MAX) {
1667        u16::MAX
1668    } else {
1669        stake_100k_whole_units.try_into().unwrap()
1670    };
1671    // We invert the key because we need high stake to appear first and it's ordered ASC
1672    Ok((u16::MAX - stake_u16).to_be_bytes())
1673}
1674
1675struct SecurifiedValidator;
1676
1677impl SecurifiedRoleAssignment for SecurifiedValidator {
1678    type OwnerBadgeNonFungibleData = ValidatorOwnerBadgeData;
1679    const OWNER_BADGE: ResourceAddress = VALIDATOR_OWNER_BADGE;
1680    const SECURIFY_ROLE: Option<&'static str> = None;
1681}
1682
1683pub(crate) struct ValidatorCreator;
1684
1685impl ValidatorCreator {
1686    fn create_stake_unit_resource<Y: SystemApi<RuntimeError>>(
1687        validator_address: GlobalAddress,
1688        api: &mut Y,
1689    ) -> Result<ResourceAddress, RuntimeError> {
1690        let stake_unit_resman = ResourceManager::new_fungible(
1691            OwnerRole::Fixed(rule!(require(global_caller(validator_address)))),
1692            true,
1693            18,
1694            FungibleResourceRoles {
1695                mint_roles: mint_roles! {
1696                    minter => rule!(require(global_caller(validator_address)));
1697                    minter_updater => rule!(deny_all);
1698                },
1699                burn_roles: burn_roles! {
1700                    burner => rule!(require(global_caller(validator_address)));
1701                    burner_updater => rule!(deny_all);
1702                },
1703                ..Default::default()
1704            },
1705            metadata_init! {
1706                "name" => "Liquid Stake Units".to_owned(), locked;
1707                "description" => "Liquid Stake Unit tokens that represent a proportion of XRD stake delegated to a Radix Network validator.".to_owned(), locked;
1708                "icon_url" => UncheckedUrl::of("https://assets.radixdlt.com/icons/icon-liquid_stake_units.png"), locked;
1709                "validator" => validator_address, locked;
1710                "tags" => Vec::<String>::new(), locked;
1711            },
1712            None,
1713            api,
1714        )?;
1715
1716        Ok(stake_unit_resman.0)
1717    }
1718
1719    fn create_claim_nft<Y: SystemApi<RuntimeError>>(
1720        validator_address: GlobalAddress,
1721        api: &mut Y,
1722    ) -> Result<ResourceAddress, RuntimeError> {
1723        let unstake_resman = ResourceManager::new_non_fungible::<UnstakeData, Y, RuntimeError, _>(
1724            OwnerRole::Fixed(rule!(require(global_caller(validator_address)))),
1725            NonFungibleIdType::RUID,
1726            true,
1727            NonFungibleResourceRoles {
1728                mint_roles: mint_roles! {
1729                    minter => rule!(require(global_caller(validator_address)));
1730                    minter_updater => rule!(deny_all);
1731                },
1732                burn_roles: burn_roles! {
1733                    burner => rule!(require(global_caller(validator_address)));
1734                    burner_updater => rule!(deny_all);
1735                },
1736                ..Default::default()
1737            },
1738            metadata_init! {
1739                "name" => "Stake Claims NFTs".to_owned(), locked;
1740                "description" => "Unique Stake Claim tokens that represent a timed claimable amount of XRD stake from a Radix Network validator.".to_owned(), locked;
1741                "icon_url" => UncheckedUrl::of("https://assets.radixdlt.com/icons/icon-stake_claim_NFTs.png"), locked;
1742                "validator" => validator_address, locked;
1743                "tags" => Vec::<String>::new(), locked;
1744            },
1745            None,
1746            api,
1747        )?;
1748
1749        Ok(unstake_resman.0)
1750    }
1751
1752    pub fn create<Y: SystemApi<RuntimeError>>(
1753        key: Secp256k1PublicKey,
1754        is_registered: bool,
1755        fee_factor: Decimal,
1756        api: &mut Y,
1757    ) -> Result<(ComponentAddress, Bucket), RuntimeError> {
1758        // check if validator fee is valid
1759        check_validator_fee_factor(fee_factor)?;
1760
1761        let (address_reservation, validator_address) =
1762            api.allocate_global_address(BlueprintId {
1763                package_address: CONSENSUS_MANAGER_PACKAGE,
1764                blueprint_name: VALIDATOR_BLUEPRINT.to_string(),
1765            })?;
1766
1767        let stake_xrd_vault = Vault::create(XRD, api)?;
1768        let pending_xrd_withdraw_vault = Vault::create(XRD, api)?;
1769        let claim_nft = Self::create_claim_nft(validator_address, api)?;
1770        let stake_unit_resource = Self::create_stake_unit_resource(validator_address, api)?;
1771        let locked_owner_stake_unit_vault = Vault::create(stake_unit_resource, api)?;
1772        let pending_owner_stake_unit_unlock_vault = Vault::create(stake_unit_resource, api)?;
1773        let pending_owner_stake_unit_withdrawals = BTreeMap::new();
1774
1775        let substate = ValidatorSubstate {
1776            sorted_key: None,
1777            key,
1778            is_registered,
1779            accepts_delegated_stake: false,
1780            validator_fee_factor: fee_factor,
1781            validator_fee_change_request: None,
1782            stake_unit_resource,
1783            claim_nft,
1784            stake_xrd_vault_id: stake_xrd_vault.0,
1785            pending_xrd_withdraw_vault_id: pending_xrd_withdraw_vault.0,
1786            locked_owner_stake_unit_vault_id: locked_owner_stake_unit_vault.0,
1787            pending_owner_stake_unit_unlock_vault_id: pending_owner_stake_unit_unlock_vault.0,
1788            pending_owner_stake_unit_withdrawals,
1789            already_unlocked_owner_stake_unit_amount: Decimal::zero(),
1790        };
1791
1792        let protocol_update_readiness_signal = ValidatorProtocolUpdateReadinessSignalSubstate {
1793            protocol_version_name: None,
1794        };
1795
1796        let validator_id = api.new_simple_object(
1797            VALIDATOR_BLUEPRINT,
1798            indexmap! {
1799                ValidatorField::State.field_index() => FieldValue::new(ValidatorStateFieldPayload::from_content_source(substate)),
1800                ValidatorField::ProtocolUpdateReadinessSignal.field_index() => FieldValue::new(ValidatorProtocolUpdateReadinessSignalFieldPayload::from_content_source(protocol_update_readiness_signal)),
1801            },
1802        )?;
1803
1804        let (role_assignment, owner_token_bucket) = SecurifiedValidator::create_securified(
1805            ValidatorOwnerBadgeData {
1806                name: "Validator Owner Badge".to_owned(),
1807                validator: validator_address.try_into().expect("Impossible Case!"),
1808            },
1809            Some(NonFungibleLocalId::bytes(validator_address.as_node_id().0).unwrap()),
1810            api,
1811        )?;
1812        let owner_badge_local_id = owner_token_bucket
1813            .non_fungible_local_ids(api)?
1814            .first()
1815            .expect("Impossible Case")
1816            .clone();
1817        let metadata = Metadata::create_with_data(
1818            metadata_init! {
1819                "owner_badge" => owner_badge_local_id, locked;
1820                "pool_unit" => GlobalAddress::from(stake_unit_resource), locked;
1821                "claim_nft" => GlobalAddress::from(claim_nft), locked;
1822            },
1823            api,
1824        )?;
1825
1826        api.globalize(
1827            validator_id,
1828            indexmap!(
1829                AttachedModuleId::RoleAssignment => role_assignment.0.0,
1830                AttachedModuleId::Metadata => metadata.0,
1831            ),
1832            Some(address_reservation),
1833        )?;
1834
1835        Ok((
1836            ComponentAddress::new_or_panic(validator_address.into()),
1837            owner_token_bucket,
1838        ))
1839    }
1840}
1841
1842#[cfg(test)]
1843mod tests {
1844    use super::*;
1845
1846    #[test]
1847    fn sort_key_is_calculated_correctly() {
1848        assert_eq!(
1849            create_sort_prefix_from_stake(Decimal::ZERO).unwrap(),
1850            u16::MAX.to_be_bytes()
1851        );
1852        assert_eq!(
1853            create_sort_prefix_from_stake(dec!(99_999)).unwrap(),
1854            u16::MAX.to_be_bytes()
1855        );
1856        assert_eq!(
1857            create_sort_prefix_from_stake(dec!(100_000)).unwrap(),
1858            (u16::MAX - 1).to_be_bytes()
1859        );
1860        assert_eq!(
1861            create_sort_prefix_from_stake(dec!(199_999)).unwrap(),
1862            (u16::MAX - 1).to_be_bytes()
1863        );
1864        assert_eq!(
1865            create_sort_prefix_from_stake(dec!(200_000)).unwrap(),
1866            (u16::MAX - 2).to_be_bytes()
1867        );
1868        // https://learn.radixdlt.com/article/start-here-radix-tokens-and-tokenomics
1869        let max_xrd_supply = dec!(24)
1870            .checked_mul(dec!(10).checked_powi(12).unwrap())
1871            .unwrap();
1872        assert_eq!(
1873            create_sort_prefix_from_stake(max_xrd_supply).unwrap(),
1874            0u16.to_be_bytes()
1875        );
1876    }
1877}
1878
1879#[derive(ScryptoSbor)]
1880pub struct ValidatorOwnerBadgeData {
1881    pub name: String,
1882    pub validator: ComponentAddress,
1883}
1884
1885impl NonFungibleData for ValidatorOwnerBadgeData {
1886    const MUTABLE_FIELDS: &'static [&'static str] = &[];
1887}