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
33pub const OWNER_STAKE_UNITS_PENDING_WITHDRAWALS_LIMIT: usize = 100;
36
37#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
38pub struct ValidatorSubstate {
39 pub sorted_key: Option<SortedKey>,
46
47 pub key: Secp256k1PublicKey,
49
50 pub is_registered: bool,
52
53 pub accepts_delegated_stake: bool,
55
56 pub validator_fee_factor: Decimal,
62
63 pub validator_fee_change_request: Option<ValidatorFeeChangeRequest>,
70
71 pub stake_unit_resource: ResourceAddress,
75
76 pub stake_xrd_vault_id: Own,
78
79 pub claim_nft: ResourceAddress,
84
85 pub pending_xrd_withdraw_vault_id: Own,
87
88 pub locked_owner_stake_unit_vault_id: Own,
95
96 pub pending_owner_stake_unit_unlock_vault_id: Own,
100
101 pub pending_owner_stake_unit_withdrawals: BTreeMap<Epoch, Decimal>,
109
110 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 pub claim_epoch: Epoch,
129
130 pub claim_amount: Decimal,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
135pub struct ValidatorFeeChangeRequest {
136 pub epoch_effective: Epoch,
144
145 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 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 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 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 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 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 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 {
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_validator_fee_factor(new_fee_factor)?;
973
974 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 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 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 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 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() }
1020 .ok_or(RuntimeError::ApplicationError(
1021 ApplicationError::ValidatorError(ValidatorError::EpochMathOverflow),
1022 ))?;
1023
1024 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 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 pub fn start_unlock_owner_stake_units<Y: SystemApi<RuntimeError>>(
1185 requested_stake_unit_amount: Decimal,
1186 api: &mut Y,
1187 ) -> Result<(), RuntimeError> {
1188 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 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 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 Self::normalize_available_owner_stake_unit_withdrawals(&mut substate, current_epoch)?;
1223
1224 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 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 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 pub fn finish_unlock_owner_stake_units<Y: SystemApi<RuntimeError>>(
1263 api: &mut Y,
1264 ) -> Result<Bucket, RuntimeError> {
1265 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 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 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 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 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 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 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 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 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 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 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 Vault(substate.locked_owner_stake_unit_vault_id).put(fee_stake_unit_bucket.into(), api)?;
1404
1405 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 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 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 let total_reward_xrd = xrd_bucket.amount(api)?;
1455
1456 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 Vault(substate.locked_owner_stake_unit_vault_id).put(new_stake_unit_bucket.into(), api)?;
1471
1472 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 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 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 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 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 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_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 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}