pchain_runtime/execution/
staking.rs

1/*
2    Copyright © 2023, ParallelChain Lab
3    Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
4*/
5
6//! Implementation of executing [Staking Commands](https://github.com/parallelchain-io/parallelchain-protocol/blob/master/Runtime.md#staking-commands).
7
8use pchain_types::blockchain::Command;
9use pchain_types::runtime::{
10    CreateDepositInput, SetDepositSettingsInput, SetPoolSettingsInput, StakeDepositInput,
11    TopUpDepositInput, UnstakeDepositInput, WithdrawDepositInput,
12};
13use pchain_types::{cryptography::PublicAddress, runtime::CreatePoolInput};
14use pchain_world_state::{
15    network::{
16        network_account::{NetworkAccount, NetworkAccountStorage},
17        pool::PoolKey,
18        stake::{Stake, StakeValue},
19    },
20    storage::WorldStateStorage,
21};
22
23use crate::cost::CostChange;
24use crate::gas;
25use crate::{transition::StateChangesResult, TransitionError};
26
27use super::state::ExecutionState;
28use super::{
29    execute::TryExecuteResult,
30    phase::{self},
31};
32
33/// Execution Logic for Staking Commands. Err If the Command is not Staking Command.
34/// It transits the state according to Metwork Command, on behalf of actor. Actor is expected
35/// to be the signer of the transaction, or the contract that triggers deferred command.
36pub(crate) fn try_execute<S>(
37    actor: PublicAddress,
38    state: ExecutionState<S>,
39    command: &Command,
40) -> TryExecuteResult<S>
41where
42    S: pchain_world_state::storage::WorldStateStorage + Send + Sync + Clone + 'static,
43{
44    let ret = match command {
45        Command::CreatePool(CreatePoolInput { commission_rate }) => {
46            create_pool(actor, state, *commission_rate)
47        }
48        Command::SetPoolSettings(SetPoolSettingsInput { commission_rate }) => {
49            set_pool_settings(actor, state, *commission_rate)
50        }
51        Command::DeletePool => delete_pool(actor, state),
52        Command::CreateDeposit(CreateDepositInput {
53            operator,
54            balance,
55            auto_stake_rewards,
56        }) => create_deposit(actor, state, *operator, *balance, *auto_stake_rewards),
57        Command::SetDepositSettings(SetDepositSettingsInput {
58            operator,
59            auto_stake_rewards,
60        }) => set_deposit_settings(actor, state, *operator, *auto_stake_rewards),
61        Command::TopUpDeposit(TopUpDepositInput { operator, amount }) => {
62            topup_deposit(actor, state, *operator, *amount)
63        }
64        Command::WithdrawDeposit(WithdrawDepositInput {
65            operator,
66            max_amount,
67        }) => withdraw_deposit(actor, state, *operator, *max_amount),
68        Command::StakeDeposit(StakeDepositInput {
69            operator,
70            max_amount,
71        }) => stake_deposit(actor, state, *operator, *max_amount),
72        Command::UnstakeDeposit(UnstakeDepositInput {
73            operator,
74            max_amount,
75        }) => unstake_deposit(actor, state, *operator, *max_amount),
76        _ => return TryExecuteResult::Err(state),
77    };
78
79    TryExecuteResult::Ok(ret)
80}
81
82/// Execution of [pchain_types::blockchain::Command::CreatePool]
83pub(crate) fn create_pool<S>(
84    operator: PublicAddress,
85    mut state: ExecutionState<S>,
86    commission_rate: u8,
87) -> Result<ExecutionState<S>, StateChangesResult<S>>
88where
89    S: WorldStateStorage + Send + Sync + Clone,
90{
91    if commission_rate > 100 {
92        return Err(phase::abort(state, TransitionError::InvalidPoolPolicy));
93    }
94
95    // Create Pool
96    let mut pool = NetworkAccount::pools(&mut state, operator);
97    if pool.exists() {
98        return Err(phase::abort(state, TransitionError::PoolAlreadyExists));
99    }
100    pool.set_operator(operator);
101    pool.set_power(0);
102    pool.set_commission_rate(commission_rate);
103    pool.set_operator_stake(None);
104
105    // Update NVP
106    let _ = NetworkAccount::nvp(&mut state).insert_extract(PoolKey { operator, power: 0 });
107
108    phase::finalize_gas_consumption(state)
109}
110
111/// Execution of [pchain_types::blockchain::Command::SetPoolSettings]
112pub(crate) fn set_pool_settings<S>(
113    operator: PublicAddress,
114    mut state: ExecutionState<S>,
115    new_commission_rate: u8,
116) -> Result<ExecutionState<S>, StateChangesResult<S>>
117where
118    S: WorldStateStorage + Send + Sync + Clone,
119{
120    if new_commission_rate > 100 {
121        return Err(phase::abort(state, TransitionError::InvalidPoolPolicy));
122    }
123
124    // Update Pool
125    let mut pool = NetworkAccount::pools(&mut state, operator);
126    if !pool.exists() {
127        return Err(phase::abort(state, TransitionError::PoolNotExists));
128    }
129
130    if pool.commission_rate() == Some(new_commission_rate) {
131        return Err(phase::abort(state, TransitionError::InvalidPoolPolicy));
132    }
133
134    pool.set_commission_rate(new_commission_rate);
135
136    phase::finalize_gas_consumption(state)
137}
138
139/// Execution of [pchain_types::blockchain::Command::DeletePool]
140pub(crate) fn delete_pool<S>(
141    operator: PublicAddress,
142    mut state: ExecutionState<S>,
143) -> Result<ExecutionState<S>, StateChangesResult<S>>
144where
145    S: WorldStateStorage + Send + Sync + Clone,
146{
147    let pool = NetworkAccount::pools(&mut state, operator);
148    if !pool.exists() {
149        return Err(phase::abort(state, TransitionError::PoolNotExists));
150    }
151
152    NetworkAccount::nvp(&mut state).remove_item(&operator);
153
154    NetworkAccount::pools(&mut state, operator).delete();
155
156    phase::finalize_gas_consumption(state)
157}
158
159/// Execution of [pchain_types::blockchain::Command::CreateDeposit]
160pub(crate) fn create_deposit<S>(
161    owner: PublicAddress,
162    mut state: ExecutionState<S>,
163    operator: PublicAddress,
164    balance: u64,
165    auto_stake_rewards: bool,
166) -> Result<ExecutionState<S>, StateChangesResult<S>>
167where
168    S: WorldStateStorage + Send + Sync + Clone,
169{
170    if !NetworkAccount::pools(&mut state, operator).exists() {
171        return Err(phase::abort(state, TransitionError::PoolNotExists));
172    }
173
174    if NetworkAccount::deposits(&mut state, operator, owner).exists() {
175        return Err(phase::abort(state, TransitionError::DepositsAlreadyExists));
176    }
177
178    let (owner_balance, _) = state.balance(owner);
179    if owner_balance < balance {
180        return Err(phase::abort(
181            state,
182            TransitionError::NotEnoughBalanceForTransfer,
183        ));
184    }
185    state.set_balance(owner, owner_balance - balance);
186
187    let mut deposits = NetworkAccount::deposits(&mut state, operator, owner);
188    deposits.set_balance(balance);
189    deposits.set_auto_stake_rewards(auto_stake_rewards);
190
191    phase::finalize_gas_consumption(state)
192}
193
194/// Execution of [pchain_types::blockchain::Command::SetDepositSettings]
195pub(crate) fn set_deposit_settings<S>(
196    owner: PublicAddress,
197    mut state: ExecutionState<S>,
198    operator: PublicAddress,
199    new_auto_stake_rewards: bool,
200) -> Result<ExecutionState<S>, StateChangesResult<S>>
201where
202    S: WorldStateStorage + Send + Sync + Clone,
203{
204    let mut deposits = NetworkAccount::deposits(&mut state, operator, owner);
205    if !deposits.exists() {
206        return Err(phase::abort(state, TransitionError::DepositsNotExists));
207    }
208
209    if deposits.auto_stake_rewards() == Some(new_auto_stake_rewards) {
210        return Err(phase::abort(state, TransitionError::InvalidDepositPolicy));
211    }
212
213    deposits.set_auto_stake_rewards(new_auto_stake_rewards);
214
215    phase::finalize_gas_consumption(state)
216}
217
218/// Execution of [pchain_types::blockchain::Command::TopUpDeposit]
219pub(crate) fn topup_deposit<S>(
220    owner: PublicAddress,
221    mut state: ExecutionState<S>,
222    operator: PublicAddress,
223    amount: u64,
224) -> Result<ExecutionState<S>, StateChangesResult<S>>
225where
226    S: WorldStateStorage + Send + Sync + Clone,
227{
228    if !NetworkAccount::deposits(&mut state, operator, owner).exists() {
229        return Err(phase::abort(state, TransitionError::DepositsNotExists));
230    }
231
232    let (owner_balance, _) = state.balance(owner);
233    if owner_balance < amount {
234        return Err(phase::abort(
235            state,
236            TransitionError::NotEnoughBalanceForTransfer,
237        ));
238    }
239    state.set_balance(owner, owner_balance - amount); // Always deduct the amount specified in the transaction
240
241    let mut deposits = NetworkAccount::deposits(&mut state, operator, owner);
242    let deposit_balance = deposits.balance().unwrap();
243    deposits.set_balance(deposit_balance.saturating_add(amount)); // Ceiling to MAX for safety. Overflow should not happen in real situation.
244
245    phase::finalize_gas_consumption(state)
246}
247
248/// Execution of [pchain_types::blockchain::Command::WithdrawDeposit]
249pub(crate) fn withdraw_deposit<S>(
250    owner: PublicAddress,
251    mut state: ExecutionState<S>,
252    operator: PublicAddress,
253    max_amount: u64,
254) -> Result<ExecutionState<S>, StateChangesResult<S>>
255where
256    S: WorldStateStorage + Send + Sync + Clone,
257{
258    // 1. Check if there is any deposit to withdraw
259    let deposits = NetworkAccount::deposits(&mut state, operator, owner);
260    if !deposits.exists() {
261        return Err(phase::abort(state, TransitionError::DepositsNotExists));
262    }
263    let deposit_balance = deposits.balance().unwrap();
264
265    // 2. Compute withdrawal amount
266    let prev_epoch_locked_power =
267        NetworkAccount::pvp(&mut state)
268            .pool(operator)
269            .map_or(0, |mut pool| {
270                if operator == owner {
271                    pool.operator_stake()
272                        .map_or(0, |stake| stake.map_or(0, |s| s.power))
273                } else {
274                    pool.delegated_stakes()
275                        .get_by(&owner)
276                        .map_or(0, |stake| stake.power)
277                }
278            });
279    let cur_epoch_locked_power =
280        NetworkAccount::vp(&mut state)
281            .pool(operator)
282            .map_or(0, |mut pool| {
283                if operator == owner {
284                    pool.operator_stake()
285                        .map_or(0, |stake| stake.map_or(0, |s| s.power))
286                } else {
287                    pool.delegated_stakes()
288                        .get_by(&owner)
289                        .map_or(0, |stake| stake.power)
290                }
291            });
292    let locked_power = std::cmp::max(prev_epoch_locked_power, cur_epoch_locked_power);
293    let withdrawal_amount = std::cmp::min(max_amount, deposit_balance.saturating_sub(locked_power));
294    let new_deposit_balance = deposit_balance.saturating_sub(withdrawal_amount);
295
296    // 3. Abort if there is no amount currently available to withdraw.
297    if new_deposit_balance == deposit_balance {
298        // e.g. max_amount = 0  or deposit_balance == locked_power
299        return Err(phase::abort(state, TransitionError::InvalidStakeAmount));
300    }
301
302    // 4. Update the deposit's balance to reflect the withdrawal.
303    if new_deposit_balance == 0 {
304        NetworkAccount::deposits(&mut state, operator, owner).delete();
305    } else {
306        NetworkAccount::deposits(&mut state, operator, owner).set_balance(new_deposit_balance);
307    }
308    let (owner_balance, _) = state.balance(owner);
309    state.set_balance(owner, owner_balance.saturating_add(deposit_balance - new_deposit_balance)); // Ceiling to MAX for safety. Overflow should not happen in real situation.
310
311    // 5. If the deposit's new balance is now too small to support its Stake in the next Epoch, cap the Stake's power at the new balance.
312    if let Some(stake_power) = stake_of_pool(&mut state, operator, owner) {
313        if new_deposit_balance < stake_power {
314            if let Some(prev_pool_power) = NetworkAccount::pools(&mut state, operator).power() {
315                reduce_stake_power(
316                    &mut state,
317                    operator,
318                    prev_pool_power,
319                    owner,
320                    stake_power,
321                    stake_power - new_deposit_balance,
322                );
323            }
324        }
325    }
326
327    // 5. Set the withdrawal amount to return_value
328    let return_value = withdrawal_amount.to_le_bytes().to_vec();
329    state.receipt_write_gas +=
330        CostChange::deduct(gas::blockchain_return_values_cost(return_value.len()));
331    if state.tx.gas_limit < state.total_gas_to_be_consumed() {
332        return Err(phase::abort(
333            state,
334            TransitionError::ExecutionProperGasExhausted,
335        ));
336    }
337    state.return_value = Some(return_value);
338
339    phase::finalize_gas_consumption(state)
340}
341
342/// Execution of [pchain_types::blockchain::Command::StakeDeposit]
343pub(crate) fn stake_deposit<S>(
344    owner: PublicAddress,
345    mut state: ExecutionState<S>,
346    operator: PublicAddress,
347    max_amount: u64,
348) -> Result<ExecutionState<S>, StateChangesResult<S>>
349where
350    S: WorldStateStorage + Send + Sync + Clone,
351{
352    // 1. Check if there is a Deposit to stake
353    if !NetworkAccount::deposits(&mut state, operator, owner).exists() {
354        return Err(phase::abort(state, TransitionError::DepositsNotExists));
355    }
356    let deposit_balance = NetworkAccount::deposits(&mut state, operator, owner)
357        .balance()
358        .unwrap();
359
360    // 2. Check if there is a Pool to stake to.
361    let pool = NetworkAccount::pools(&mut state, operator);
362    if !pool.exists() {
363        return Err(phase::abort(state, TransitionError::PoolNotExists));
364    }
365    let prev_pool_power = pool.power().unwrap();
366
367    // We use this to update the Pool's power after the power of one of its stakes get increased.
368    let stake_power = stake_of_pool(&mut state, operator, owner);
369
370    let stake_power_to_increase = std::cmp::min(
371        max_amount,
372        deposit_balance.saturating_sub(stake_power.unwrap_or(0)),
373    );
374    if stake_power_to_increase == 0 {
375        return Err(phase::abort(state, TransitionError::InvalidStakeAmount));
376    }
377
378    // Update Stakes and the Pool's power and its position in the Next Validator Set.
379    match increase_stake_power(
380        &mut state,
381        operator,
382        prev_pool_power,
383        owner,
384        stake_power,
385        stake_power_to_increase,
386        true,
387    ) {
388        Ok(_) => {}
389        Err(_) => return Err(phase::abort(state, TransitionError::InvalidStakeAmount)),
390    };
391
392    // Set the staked amount to return_value
393    let return_value = stake_power_to_increase.to_le_bytes().to_vec();
394    state.receipt_write_gas +=
395        CostChange::deduct(gas::blockchain_return_values_cost(return_value.len()));
396    if state.tx.gas_limit < state.total_gas_to_be_consumed() {
397        return Err(phase::abort(
398            state,
399            TransitionError::ExecutionProperGasExhausted,
400        ));
401    }
402    state.return_value = Some(return_value);
403
404    phase::finalize_gas_consumption(state)
405}
406
407/// Execution of [pchain_types::blockchain::Command::UnstakeDeposit]
408pub(crate) fn unstake_deposit<S>(
409    owner: PublicAddress,
410    mut state: ExecutionState<S>,
411    operator: PublicAddress,
412    max_amount: u64,
413) -> Result<ExecutionState<S>, StateChangesResult<S>>
414where
415    S: pchain_world_state::storage::WorldStateStorage + Send + Sync + Clone,
416{
417    // 1. Check if there is a Deposit to unstake.
418    if !NetworkAccount::deposits(&mut state, operator, owner).exists() {
419        return Err(phase::abort(state, TransitionError::DepositsNotExists));
420    }
421
422    // 2. If there is no Pool, then there is no Stake to unstake.
423    let pool = NetworkAccount::pools(&mut state, operator);
424    if !pool.exists() {
425        return Err(phase::abort(state, TransitionError::PoolNotExists));
426    }
427    let prev_pool_power = pool.power().unwrap();
428
429    let stake_power = match stake_of_pool(&mut state, operator, owner) {
430        Some(stake_power) => stake_power,
431        None => return Err(phase::abort(state, TransitionError::PoolHasNoStakes)),
432    };
433
434    // 3. Reduce the Stake's power.
435    let amount_unstaked = reduce_stake_power(
436        &mut state,
437        operator,
438        prev_pool_power,
439        owner,
440        stake_power,
441        max_amount,
442    );
443
444    // 4. set the unstaked amount to return_value
445    let return_value = amount_unstaked.to_le_bytes().to_vec();
446    state.receipt_write_gas +=
447        CostChange::deduct(gas::blockchain_return_values_cost(return_value.len()));
448    if state.tx.gas_limit < state.total_gas_to_be_consumed() {
449        return Err(phase::abort(
450            state,
451            TransitionError::ExecutionProperGasExhausted,
452        ));
453    }
454    state.return_value = Some(return_value);
455
456    phase::finalize_gas_consumption(state)
457}
458
459/// return owner's stake from operator's pool (NVS)
460pub(crate) fn stake_of_pool<T>(
461    state: &mut T,
462    operator: PublicAddress,
463    owner: PublicAddress,
464) -> Option<u64>
465where
466    T: NetworkAccountStorage,
467{
468    if operator == owner {
469        match NetworkAccount::pools(state, operator).operator_stake() {
470            Some(Some(stake)) => Some(stake.power),
471            _ => None,
472        }
473    } else {
474        NetworkAccount::pools(state, operator)
475            .delegated_stakes()
476            .get_by(&owner)
477            .map(|stake| stake.power)
478    }
479}
480
481/// Reduce stake's power and update Pool position in Next validator set.
482pub(crate) fn reduce_stake_power<T>(
483    state: &mut T,
484    operator: PublicAddress,
485    pool_power: u64,
486    owner: PublicAddress,
487    stake_power: u64,
488    reduce_amount: u64,
489) -> u64
490where
491    T: NetworkAccountStorage,
492{
493    // Reduce the Stake's power.
494    let amount_unstaked = if stake_power <= reduce_amount {
495        // If the Stake's power is less than the amount to be reduced, remove the Stake.
496        if operator == owner {
497            NetworkAccount::pools(state, operator).set_operator_stake(None);
498        } else {
499            NetworkAccount::pools(state, operator)
500                .delegated_stakes()
501                .remove_item(&owner);
502        }
503        stake_power
504    } else {
505        // Otherwise, reduce the Stake's power.
506        let new_state = Stake {
507            owner,
508            power: stake_power - reduce_amount,
509        };
510        if operator == owner {
511            NetworkAccount::pools(state, operator).set_operator_stake(Some(new_state));
512        } else {
513            NetworkAccount::pools(state, operator)
514                .delegated_stakes()
515                .change_key(StakeValue::new(new_state));
516        }
517        reduce_amount
518    };
519    let new_pool_power = pool_power.saturating_sub(amount_unstaked);
520
521    // Update the Pool's power and its position in the Next Validator Set.
522    NetworkAccount::pools(state, operator).set_power(new_pool_power);
523    match NetworkAccount::nvp(state).get_by(&operator) {
524        Some(mut pool_key) => {
525            if new_pool_power == 0 {
526                NetworkAccount::nvp(state).remove_item(&operator);
527            } else {
528                pool_key.power = new_pool_power;
529                NetworkAccount::nvp(state).change_key(pool_key);
530            }
531        }
532        None => {
533            if new_pool_power > 0 {
534                let _ = NetworkAccount::nvp(state).insert_extract(PoolKey {
535                    operator,
536                    power: new_pool_power,
537                });
538            }
539        }
540    }
541    amount_unstaked
542}
543
544/// increase_stake_power increases stake's power and also update the NVP.
545// 1a. pool[i].delegated_stakes[j] .change_key or .insert_extract
546// 1b. pool[i].operator_stake += v
547// 2. pool[i].power += v
548// 3. nas.pool[i] .change_key or insert_extract
549pub(crate) fn increase_stake_power<T>(
550    state: &mut T,
551    operator: PublicAddress,
552    pool_power: u64,
553    owner: PublicAddress,
554    stake_power: Option<u64>,
555    stake_power_to_increase: u64,
556    exit_on_insert_fail: bool,
557) -> Result<(), ()>
558where
559    T: NetworkAccountStorage,
560{
561    let mut pool = NetworkAccount::pools(state, operator);
562
563    let power_to_add = if operator == owner {
564        let stake_power = stake_power.unwrap_or(0);
565        pool.set_operator_stake(Some(Stake {
566            owner: operator,
567            power: stake_power.saturating_add(stake_power_to_increase),
568        }));
569        stake_power_to_increase
570    } else {
571        let mut delegated_stakes = pool.delegated_stakes();
572        match stake_power {
573            Some(stake_power) => {
574                delegated_stakes.change_key(StakeValue::new(Stake {
575                    owner,
576                    power: stake_power.saturating_add(stake_power_to_increase),
577                }));
578                stake_power_to_increase
579            }
580            None => {
581                match delegated_stakes.insert_extract(StakeValue::new(Stake {
582                    owner,
583                    power: stake_power_to_increase,
584                })) {
585                    Ok(Some(replaced_stake)) => stake_power_to_increase.saturating_sub(replaced_stake.power),
586                    Ok(None) => stake_power_to_increase,
587                    Err(_) => {
588                        if exit_on_insert_fail {
589                            return Err(());
590                        }
591                        stake_power_to_increase
592                    }
593                }
594            }
595        }
596    };
597
598    let new_pool_power = pool_power.saturating_add(power_to_add);
599    pool.set_power(new_pool_power);
600    match NetworkAccount::nvp(state).get_by(&operator) {
601        Some(mut pool_key) => {
602            pool_key.power = new_pool_power;
603            NetworkAccount::nvp(state).change_key(pool_key);
604        }
605        None => {
606            let _ = NetworkAccount::nvp(state).insert_extract(PoolKey {
607                operator,
608                power: new_pool_power,
609            });
610        }
611    }
612    Ok(())
613}