1use 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
33pub(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
82pub(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 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 let _ = NetworkAccount::nvp(&mut state).insert_extract(PoolKey { operator, power: 0 });
107
108 phase::finalize_gas_consumption(state)
109}
110
111pub(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 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
139pub(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
159pub(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
194pub(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
218pub(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); 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)); phase::finalize_gas_consumption(state)
246}
247
248pub(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 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 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 if new_deposit_balance == deposit_balance {
298 return Err(phase::abort(state, TransitionError::InvalidStakeAmount));
300 }
301
302 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)); 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 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
342pub(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 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 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 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 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 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
407pub(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 if !NetworkAccount::deposits(&mut state, operator, owner).exists() {
419 return Err(phase::abort(state, TransitionError::DepositsNotExists));
420 }
421
422 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 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 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
459pub(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
481pub(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 let amount_unstaked = if stake_power <= reduce_amount {
495 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 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 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
544pub(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}