1+ /// This module allows for the dynamic rebalancing of validator stakes based
2+ /// on a set of validator addresses and weights. Rebalance can be called permissionlessly
3+ module liquid_staking ::weight {
4+ use sui_system::sui_system::{SuiSystemState };
5+ use liquid_staking::liquid_staking::{LiquidStakingInfo , AdminCap };
6+ use sui::vec_map::{Self , VecMap };
7+ use sui::bag::{Self , Bag };
8+ use liquid_staking::version::{Self , Version };
9+
10+ /* Constants */
11+ const CURRENT_VERSION : u16 = 1 ;
12+
13+ public struct WeightHook <phantom P > has key , store {
14+ id: UID ,
15+ validator_addresses_and_weights: VecMap <address , u64 >,
16+ total_weight: u64 ,
17+ admin_cap: AdminCap <P >,
18+ version: Version ,
19+ extra_fields: Bag
20+ }
21+
22+ public struct WeightHookAdminCap <phantom P > has key , store {
23+ id: UID
24+ }
25+
26+ public fun new <P >(
27+ admin_cap: AdminCap <P >,
28+ ctx: &mut TxContext
29+ ): (WeightHook <P >, WeightHookAdminCap <P >) {
30+ (
31+ WeightHook {
32+ id: object::new (ctx),
33+ validator_addresses_and_weights: vec_map::empty (),
34+ total_weight: 0 ,
35+ admin_cap,
36+ version: version::new (CURRENT_VERSION ),
37+ extra_fields: bag::new (ctx)
38+ },
39+ WeightHookAdminCap { id: object::new (ctx) }
40+ )
41+ }
42+
43+ public fun set_validator_addresses_and_weights <P >(
44+ self: &mut WeightHook <P >,
45+ _: &WeightHookAdminCap <P >,
46+ validator_addresses_and_weights: VecMap <address , u64 >,
47+ ) {
48+ self.version.assert_version_and_upgrade (CURRENT_VERSION );
49+ self.validator_addresses_and_weights = validator_addresses_and_weights;
50+
51+ let mut total_weight = 0 ;
52+ self.validator_addresses_and_weights.keys ().length ().do ! (|i| {
53+ let (_, weight) = self.validator_addresses_and_weights.get_entry_by_idx (i);
54+ total_weight = total_weight + *weight;
55+ });
56+
57+ self.total_weight = total_weight;
58+ }
59+
60+ public fun rebalance <P >(
61+ self: &mut WeightHook <P >,
62+ system_state: &mut SuiSystemState ,
63+ liquid_staking_info: &mut LiquidStakingInfo <P >,
64+ ctx: &mut TxContext
65+ ) {
66+ self.version.assert_version_and_upgrade (CURRENT_VERSION );
67+ if (self.total_weight == 0 ) {
68+ return
69+ };
70+
71+ liquid_staking_info.refresh (system_state, ctx);
72+
73+ let mut validator_addresses_and_weights = self.validator_addresses_and_weights;
74+
75+ // 1. add all validators that exist in lst_info.validators() to the validator_address_to_weight map if they don't already exist
76+ liquid_staking_info.storage ().validators ().do_ref ! (|validator| {
77+ let validator_address = validator.validator_address ();
78+ if (!validator_addresses_and_weights.contains (&validator_address)) {
79+ validator_addresses_and_weights.insert (validator_address, 0 );
80+ };
81+ });
82+
83+ // 2. calculate current and target amounts of sui for each validator
84+ let (validator_addresses, validator_weights) = validator_addresses_and_weights.into_keys_values ();
85+
86+ let total_sui_supply = liquid_staking_info.storage ().total_sui_supply (); // we want to allocate the unaccrued spread fees as well
87+
88+ let validator_target_amounts = validator_weights.map ! (|weight| {
89+ ((total_sui_supply as u128 ) * (weight as u128 ) / (self.total_weight as u128 )) as u64
90+ });
91+
92+ let validator_current_amounts = validator_addresses.map_ref ! (|validator_address| {
93+ let validator_index = liquid_staking_info.storage ().find_validator_index_by_address (*validator_address);
94+ if (validator_index >= liquid_staking_info.storage ().validators ().length ()) {
95+ return 0
96+ };
97+
98+ let validator = liquid_staking_info.storage ().validators ().borrow (validator_index);
99+ validator.total_sui_amount ()
100+ });
101+
102+ // 3. decrease the stake for validators that have more stake than the target amount
103+ validator_addresses.length ().do ! (|i| {
104+ if (validator_current_amounts[i] > validator_target_amounts[i]) {
105+ liquid_staking_info.decrease_validator_stake (
106+ &self.admin_cap,
107+ system_state,
108+ validator_addresses[i],
109+ validator_current_amounts[i] - validator_target_amounts[i],
110+ ctx
111+ );
112+ };
113+ });
114+
115+ // 4. increase the stake for validators that have less stake than the target amount
116+ validator_addresses.length ().do ! (|i| {
117+ if (validator_current_amounts[i] < validator_target_amounts[i]) {
118+ liquid_staking_info.increase_validator_stake (
119+ &self.admin_cap,
120+ system_state,
121+ validator_addresses[i],
122+ validator_target_amounts[i] - validator_current_amounts[i],
123+ ctx
124+ );
125+ };
126+ });
127+ }
128+
129+ public fun eject <P >(
130+ mut self: WeightHook <P >,
131+ admin_cap: WeightHookAdminCap <P >,
132+ ): AdminCap <P > {
133+ self.version.assert_version_and_upgrade (CURRENT_VERSION );
134+
135+ let WeightHookAdminCap { id } = admin_cap;
136+ object::delete (id);
137+
138+ let WeightHook { id, admin_cap, extra_fields, .. } = self;
139+ extra_fields.destroy_empty ();
140+ object::delete (id);
141+
142+ admin_cap
143+ }
144+ }
0 commit comments