unbond functionality added
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
		
							parent
							
								
									b1ff74c637
								
							
						
					
					
						commit
						0777e6ebf0
					
				| @ -58,6 +58,7 @@ pub enum Action { | |||||||
|     SetSessionKeys([u8; 32], String), |     SetSessionKeys([u8; 32], String), | ||||||
|     ValidateFrom([u8; 32], u32), |     ValidateFrom([u8; 32], u32), | ||||||
|     ChillFrom([u8; 32]), |     ChillFrom([u8; 32]), | ||||||
|  |     UnbondFrom([u8; 32], u128), | ||||||
|     EventLog(String, ActionLevel, ActionTarget), |     EventLog(String, ActionLevel, ActionTarget), | ||||||
| 
 | 
 | ||||||
|     NewBestBlock(u32), |     NewBestBlock(u32), | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ mod payout_popup; | |||||||
| mod rotate_popup; | mod rotate_popup; | ||||||
| mod validate_popup; | mod validate_popup; | ||||||
| mod chill_popup; | mod chill_popup; | ||||||
|  | mod unbond_popup; | ||||||
| 
 | 
 | ||||||
| use stash_details::StashDetails; | use stash_details::StashDetails; | ||||||
| use staking_details::StakingDetails; | use staking_details::StakingDetails; | ||||||
| @ -42,6 +43,7 @@ use payout_popup::PayoutPopup; | |||||||
| use rotate_popup::RotatePopup; | use rotate_popup::RotatePopup; | ||||||
| use validate_popup::ValidatePopup; | use validate_popup::ValidatePopup; | ||||||
| use chill_popup::ChillPopup; | use chill_popup::ChillPopup; | ||||||
|  | use unbond_popup::UnbondPopup; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Copy, Clone, PartialEq)] | #[derive(Debug, Copy, Clone, PartialEq)] | ||||||
| pub enum CurrentTab { | pub enum CurrentTab { | ||||||
| @ -58,6 +60,7 @@ pub enum CurrentTab { | |||||||
|     RotatePopup, |     RotatePopup, | ||||||
|     ValidatePopup, |     ValidatePopup, | ||||||
|     ChillPopup, |     ChillPopup, | ||||||
|  |     UnbondPopup, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub trait PartialComponent: Component { | pub trait PartialComponent: Component { | ||||||
| @ -93,6 +96,7 @@ impl Default for Validator { | |||||||
|                 Box::new(RotatePopup::default()), |                 Box::new(RotatePopup::default()), | ||||||
|                 Box::new(ValidatePopup::default()), |                 Box::new(ValidatePopup::default()), | ||||||
|                 Box::new(ChillPopup::default()), |                 Box::new(ChillPopup::default()), | ||||||
|  |                 Box::new(UnbondPopup::default()), | ||||||
|             ], |             ], | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -155,6 +159,7 @@ impl Component for Validator { | |||||||
|                 CurrentTab::RotatePopup | |                 CurrentTab::RotatePopup | | ||||||
|                 CurrentTab::ValidatePopup | |                 CurrentTab::ValidatePopup | | ||||||
|                 CurrentTab::ChillPopup | |                 CurrentTab::ChillPopup | | ||||||
|  |                 CurrentTab::UnbondPopup | | ||||||
|                 CurrentTab::PayoutPopup => match key.code { |                 CurrentTab::PayoutPopup => match key.code { | ||||||
|                 KeyCode::Esc => { |                 KeyCode::Esc => { | ||||||
|                     self.current_tab = self.previous_tab; |                     self.current_tab = self.previous_tab; | ||||||
| @ -203,6 +208,13 @@ impl Component for Validator { | |||||||
|                         component.set_active(self.current_tab.clone()); |                         component.set_active(self.current_tab.clone()); | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|  |                 KeyCode::Char('U') => { | ||||||
|  |                     self.previous_tab = self.current_tab; | ||||||
|  |                     self.current_tab = CurrentTab::UnbondPopup; | ||||||
|  |                     for component in self.components.iter_mut() { | ||||||
|  |                         component.set_active(self.current_tab.clone()); | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|                 KeyCode::Char('C') => { |                 KeyCode::Char('C') => { | ||||||
|                     self.previous_tab = self.current_tab; |                     self.previous_tab = self.current_tab; | ||||||
|                     self.current_tab = CurrentTab::ChillPopup; |                     self.current_tab = CurrentTab::ChillPopup; | ||||||
|  | |||||||
							
								
								
									
										181
									
								
								src/components/validator/unbond_popup.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								src/components/validator/unbond_popup.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,181 @@ | |||||||
|  | use crossterm::event::{KeyCode, KeyEvent, KeyEventKind}; | ||||||
|  | use color_eyre::Result; | ||||||
|  | use ratatui::{ | ||||||
|  |     layout::{Position, Alignment, Constraint, Flex, Layout, Rect}, 
 | ||||||
|  |     widgets::{Block, Clear, Paragraph}, 
 | ||||||
|  |     Frame | ||||||
|  | }; | ||||||
|  | use tokio::sync::mpsc::UnboundedSender; | ||||||
|  | use std::sync::mpsc::Sender; | ||||||
|  | 
 | ||||||
|  | use super::{Component, PartialComponent, CurrentTab}; | ||||||
|  | use crate::{ | ||||||
|  |     action::Action, 
 | ||||||
|  |     config::Config, 
 | ||||||
|  |     palette::StylePalette, 
 | ||||||
|  |     types::{ActionLevel, ActionTarget}, 
 | ||||||
|  |     widgets::{Input, InputRequest}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub struct UnbondPopup { | ||||||
|  |     is_active: bool, | ||||||
|  |     action_tx: Option<UnboundedSender<Action>>, | ||||||
|  |     network_tx: Option<Sender<Action>>, | ||||||
|  |     secret_seed: [u8; 32], | ||||||
|  |     is_bonded: bool, | ||||||
|  |     amount: Input, | ||||||
|  |     palette: StylePalette | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for UnbondPopup { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self::new() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl UnbondPopup { | ||||||
|  |     pub fn new() -> Self { | ||||||
|  |         Self { | ||||||
|  |             is_active: false, | ||||||
|  |             secret_seed: [0u8; 32], | ||||||
|  |             action_tx: None, | ||||||
|  |             network_tx: None, | ||||||
|  |             is_bonded: false, | ||||||
|  |             amount: Input::new(String::new()), | ||||||
|  |             palette: StylePalette::default(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn log_event(&mut self, message: String, level: ActionLevel) { | ||||||
|  |         if let Some(action_tx) = &self.action_tx { | ||||||
|  |             let _ = action_tx.send( | ||||||
|  |                 Action::EventLog(message, level, ActionTarget::ValidatorLog)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn submit_message(&mut self) { | ||||||
|  |         if let Some(network_tx) = &self.network_tx { 
 | ||||||
|  |             match self.amount.value().parse::<f64>() { | ||||||
|  |                 Ok(value) => { | ||||||
|  |                     if self.is_bonded { | ||||||
|  |                         let amount = (value * 1_000_000_000_000_000_000.0) as u128; | ||||||
|  |                         let _ = network_tx.send(Action::UnbondFrom( | ||||||
|  |                                 self.secret_seed, amount)); | ||||||
|  |                     } else { | ||||||
|  |                         self.log_event( | ||||||
|  |                             format!("current stash doesn't have bond yet"), 
 | ||||||
|  |                             ActionLevel::Warn); | ||||||
|  |                     } | ||||||
|  |                     if let Some(action_tx) = &self.action_tx { | ||||||
|  |                         let _ = action_tx.send(Action::ClosePopup); | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 Err(err) => self.log_event( | ||||||
|  |                     format!("invalid amount, error: {err}"), ActionLevel::Error), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn enter_char(&mut self, new_char: char) { | ||||||
|  |         let is_separator_needed = !self.amount.value().contains('.') && new_char == '.'; | ||||||
|  |         if new_char.is_digit(10) || is_separator_needed { | ||||||
|  |             let _ = self.amount.handle(InputRequest::InsertChar(new_char)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn delete_char(&mut self) { | ||||||
|  |         let _ = self.amount.handle(InputRequest::DeletePrevChar); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn move_cursor_right(&mut self) { | ||||||
|  |         let _ = self.amount.handle(InputRequest::GoToNextChar); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn move_cursor_left(&mut self) { | ||||||
|  |         let _ = self.amount.handle(InputRequest::GoToPrevChar); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl PartialComponent for UnbondPopup { | ||||||
|  |     fn set_active(&mut self, current_tab: CurrentTab) { | ||||||
|  |         match current_tab { | ||||||
|  |             CurrentTab::UnbondPopup => self.is_active = true, | ||||||
|  |             _ => { | ||||||
|  |                 self.is_active = false; | ||||||
|  |                 self.amount = Input::new(String::new()); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Component for UnbondPopup { | ||||||
|  |     fn register_network_handler(&mut self, tx: Sender<Action>) -> Result<()> { | ||||||
|  |         self.network_tx = Some(tx); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> { | ||||||
|  |         self.action_tx = Some(tx); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn register_config_handler(&mut self, config: Config) -> Result<()> { | ||||||
|  |         if let Some(style) = config.styles.get(&crate::app::Mode::Wallet) { | ||||||
|  |             self.palette.with_normal_style(style.get("normal_style").copied()); | ||||||
|  |             self.palette.with_normal_border_style(style.get("normal_border_style").copied()); | ||||||
|  |             self.palette.with_normal_title_style(style.get("normal_title_style").copied()); | ||||||
|  |             self.palette.with_popup_style(style.get("popup_style").copied()); | ||||||
|  |             self.palette.with_popup_title_style(style.get("popup_title_style").copied()); | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  |     fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> { | ||||||
|  |         if self.is_active && key.kind == KeyEventKind::Press { | ||||||
|  |             match key.code { | ||||||
|  |                 KeyCode::Enter => self.submit_message(), | ||||||
|  |                 KeyCode::Char(to_insert) => self.enter_char(to_insert), | ||||||
|  |                 KeyCode::Backspace => self.delete_char(), | ||||||
|  |                 KeyCode::Left => self.move_cursor_left(), | ||||||
|  |                 KeyCode::Right => self.move_cursor_right(), | ||||||
|  |                 KeyCode::Esc => self.is_active = false, 
 | ||||||
|  |                 _ => {}, | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |         Ok(None) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn update(&mut self, action: Action) -> Result<Option<Action>> { | ||||||
|  |         match action { | ||||||
|  |             Action::SetIsBonded(is_bonded) => self.is_bonded = is_bonded, | ||||||
|  |             Action::SetStashSecret(secret_seed) => self.secret_seed = secret_seed, | ||||||
|  |             _ => {} | ||||||
|  |         }; | ||||||
|  |         Ok(None) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { | ||||||
|  |         if self.is_active { | ||||||
|  |             let (border_style, border_type) = self.palette.create_popup_style(); | ||||||
|  |             let input = Paragraph::new(self.amount.value()) | ||||||
|  |                 .block(Block::bordered() | ||||||
|  |                     .border_style(border_style) | ||||||
|  |                     .border_type(border_type) | ||||||
|  |                     .title_style(self.palette.create_popup_title_style()) | ||||||
|  |                     .title_alignment(Alignment::Right) | ||||||
|  |                     .title(format!("Unbond amount"))); | ||||||
|  |             let v = Layout::vertical([Constraint::Max(3)]).flex(Flex::Center); | ||||||
|  |             let h = Layout::horizontal([Constraint::Max(50)]).flex(Flex::Center); | ||||||
|  |             let [area] = v.areas(area); | ||||||
|  |             let [area] = h.areas(area); | ||||||
|  | 
 | ||||||
|  |             frame.render_widget(Clear, area); | ||||||
|  |             frame.render_widget(input, area); | ||||||
|  |             frame.set_cursor_position(Position::new( | ||||||
|  |                     area.x + self.amount.cursor() as u16 + 1, | ||||||
|  |                     area.y + 1 | ||||||
|  |             )); | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -178,25 +178,21 @@ impl Component for Withdrawals { | |||||||
|             self.unlockings |             self.unlockings | ||||||
|                 .iter() |                 .iter() | ||||||
|                 .map(|(key, value)| { |                 .map(|(key, value)| { | ||||||
|                     let mut era_index_text = Text::from(key.to_string()).alignment(Alignment::Left); |  | ||||||
|                     let mut est_era_text = Text::from(self.estimate_time(*key)).alignment(Alignment::Center); |                     let mut est_era_text = Text::from(self.estimate_time(*key)).alignment(Alignment::Center); | ||||||
|                     let mut value_text = Text::from(self.prepare_u128(*value)).alignment(Alignment::Right); |                     let mut value_text = Text::from(self.prepare_u128(*value)).alignment(Alignment::Right); | ||||||
| 
 | 
 | ||||||
|                     if *key > self.current_era { |                     if *key > self.current_era { | ||||||
|                         era_index_text = era_index_text.add_modifier(Modifier::CROSSED_OUT); |  | ||||||
|                         est_era_text = est_era_text.add_modifier(Modifier::CROSSED_OUT); |                         est_era_text = est_era_text.add_modifier(Modifier::CROSSED_OUT); | ||||||
|                         value_text = value_text.add_modifier(Modifier::CROSSED_OUT); |                         value_text = value_text.add_modifier(Modifier::CROSSED_OUT); | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     Row::new(vec![ |                     Row::new(vec![ | ||||||
|                         Cell::from(era_index_text), |  | ||||||
|                         Cell::from(est_era_text), |                         Cell::from(est_era_text), | ||||||
|                         Cell::from(value_text), |                         Cell::from(value_text), | ||||||
|                     ]) |                     ]) | ||||||
|                 }), |                 }), | ||||||
|             [ |             [ | ||||||
|                 Constraint::Length(12), |                 Constraint::Length(7), | ||||||
|                 Constraint::Length(13), |  | ||||||
|                 Constraint::Min(0), |                 Constraint::Min(0), | ||||||
|             ], |             ], | ||||||
|         ) |         ) | ||||||
|  | |||||||
| @ -366,6 +366,24 @@ impl Network { | |||||||
|                 } |                 } | ||||||
|                 Ok(()) |                 Ok(()) | ||||||
|             } |             } | ||||||
|  |             Action::UnbondFrom(sender, amount) => { | ||||||
|  |                 let sender_str = hex::encode(sender); | ||||||
|  |                 let maybe_nonce = self.senders.get_mut(&sender_str); | ||||||
|  |                 if let Ok(tx_progress) = predefined_txs::unbond( | ||||||
|  |                     &self.action_tx, | ||||||
|  |                     &self.online_client_api, | ||||||
|  |                     &sender, | ||||||
|  |                     &amount, | ||||||
|  |                     maybe_nonce, | ||||||
|  |                 ).await { | ||||||
|  |                     self.transactions_to_watch.push(TxToWatch { | ||||||
|  |                         tx_progress, | ||||||
|  |                         sender: sender_str, | ||||||
|  |                         target: ActionTarget::ValidatorLog, | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |                 Ok(()) | ||||||
|  |             } | ||||||
|             _ => Ok(()) |             _ => Ok(()) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -362,3 +362,48 @@ pub async fn chill( | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | pub async fn unbond( | ||||||
|  |     action_tx: &UnboundedSender<Action>, | ||||||
|  |     api: &OnlineClient<CasperConfig>, | ||||||
|  |     sender: &[u8; 32], | ||||||
|  |     amount: &u128, | ||||||
|  |     mut maybe_nonce: Option<&mut u32>, | ||||||
|  | ) -> Result<TxProgress<CasperConfig, OnlineClient<CasperConfig>>> { | ||||||
|  |     let transfer_tx = casper_network::tx() | ||||||
|  |         .staking() | ||||||
|  |         .unbond(*amount); | ||||||
|  | 
 | ||||||
|  |     let tx_params = match maybe_nonce { | ||||||
|  |         Some(ref mut nonce) => { | ||||||
|  |             **nonce = nonce.saturating_add(1); | ||||||
|  |             CasperExtrinsicParamsBuilder::new() | ||||||
|  |                 .nonce(nonce.saturating_sub(1) as u64) | ||||||
|  |                 .build() | ||||||
|  |         }, | ||||||
|  |         None => CasperExtrinsicParamsBuilder::new().build(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let pair = Pair::from_seed(sender); | ||||||
|  |     let signer = PairSigner::<CasperConfig, Pair>::new(pair); | ||||||
|  | 
 | ||||||
|  |     match api | ||||||
|  |         .tx() | ||||||
|  |         .sign_and_submit_then_watch(&transfer_tx, &signer, tx_params) | ||||||
|  |         .await { | ||||||
|  |             Ok(tx_progress) => { | ||||||
|  |                 action_tx.send(Action::EventLog( | ||||||
|  |                         format!("unbond {} sent", tx_progress.extrinsic_hash()), | ||||||
|  |                         ActionLevel::Info, | ||||||
|  |                         ActionTarget::ValidatorLog))?; | ||||||
|  |                 Ok(tx_progress) | ||||||
|  |             }, | ||||||
|  |             Err(err) => { | ||||||
|  |                 action_tx.send(Action::EventLog( | ||||||
|  |                         format!("error during unbond: {err}"), 
 | ||||||
|  |                         ActionLevel::Error, | ||||||
|  |                         ActionTarget::ValidatorLog))?; | ||||||
|  |                 Err(err.into()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user