diff --git a/.changelog/unreleased/improvements/1888-ledger-test-vector-generator-fix-0.22.0.md b/.changelog/unreleased/improvements/1888-ledger-test-vector-generator-fix-0.22.0.md new file mode 100644 index 0000000000..0aeb7196d1 --- /dev/null +++ b/.changelog/unreleased/improvements/1888-ledger-test-vector-generator-fix-0.22.0.md @@ -0,0 +1,2 @@ +- Updated the generation of hardware wallet test vectors to cover current + codebase ([\#1888](https://github.com/anoma/namada/pull/1888)) \ No newline at end of file diff --git a/sdk/src/signing.rs b/sdk/src/signing.rs index e8463db4be..381df34634 100644 --- a/sdk/src/signing.rs +++ b/sdk/src/signing.rs @@ -1,5 +1,6 @@ //! Functions to sign transactions use std::collections::{BTreeMap, HashMap}; +use std::fmt::Display; use borsh::BorshDeserialize; use borsh_ext::BorshSerializeExt; @@ -35,6 +36,10 @@ use zeroize::Zeroizing; use super::masp::{ShieldedContext, ShieldedTransfer}; use crate::args::SdkTypes; +use crate::core::ledger::governance::storage::proposal::ProposalType; +use crate::core::ledger::governance::storage::vote::{ + StorageProposalVote, VoteType, +}; use crate::error::{EncodingError, Error, TxError}; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc_proto::google::protobuf::Any; @@ -45,8 +50,8 @@ use crate::rpc::{query_wasm_code_hash, validate_amount}; use crate::tx::{ TX_BOND_WASM, TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL, TX_INIT_VALIDATOR_WASM, TX_REVEAL_PK, TX_TRANSFER_WASM, - TX_UNBOND_WASM, TX_UPDATE_ACCOUNT_WASM, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, - VP_USER_WASM, + TX_UNBOND_WASM, TX_UNJAIL_VALIDATOR_WASM, TX_UPDATE_ACCOUNT_WASM, + TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, VP_USER_WASM, }; pub use crate::wallet::store::AddressVpType; use crate::wallet::{Wallet, WalletIo}; @@ -587,11 +592,20 @@ fn make_ledger_amount_addr( prefix: &str, ) { if let Some(token) = tokens.get(token) { - output.push(format!("{}Amount {}: {}", prefix, token, amount)); + output.push(format!( + "{}Amount : {} {}", + prefix, + token.to_uppercase(), + to_ledger_decimal(&amount.to_string()), + )); } else { output.extend(vec![ - format!("{}Token: {}", prefix, token), - format!("{}Amount: {}", prefix, amount), + format!("{}Token : {}", prefix, token), + format!( + "{}Amount : {}", + prefix, + to_ledger_decimal(&amount.to_string()) + ), ]); } } @@ -611,21 +625,31 @@ async fn make_ledger_amount_asset<'a>( // If the AssetType can be decoded, then at least display Addressees let formatted_amt = context.format_amount(token, amount.into()).await; if let Some(token) = tokens.get(token) { - output - .push( - format!("{}Amount: {} {}", prefix, token, formatted_amt,), - ); + output.push(format!( + "{}Amount : {} {}", + prefix, + token.to_uppercase(), + to_ledger_decimal(&formatted_amt), + )); } else { output.extend(vec![ - format!("{}Token: {}", prefix, token), - format!("{}Amount: {}", prefix, formatted_amt), + format!("{}Token : {}", prefix, token), + format!( + "{}Amount : {}", + prefix, + to_ledger_decimal(&formatted_amt) + ), ]); } } else { // Otherwise display the raw AssetTypes output.extend(vec![ - format!("{}Token: {}", prefix, token), - format!("{}Amount: {}", prefix, amount), + format!("{}Token : {}", prefix, token), + format!( + "{}Amount : {}", + prefix, + to_ledger_decimal(&amount.to_string()) + ), ]); } } @@ -633,54 +657,44 @@ async fn make_ledger_amount_asset<'a>( /// Split the lines in the vector that are longer than the Ledger device's /// character width fn format_outputs(output: &mut Vec) { - const LEDGER_WIDTH: usize = 60; + const MAX_KEY_LEN: usize = 39; + const MAX_VALUE_LEN: usize = 39; let mut i = 0; let mut pos = 0; // Break down each line that is too long one-by-one while pos < output.len() { - let prefix_len = i.to_string().len() + 3; let curr_line = output[pos].clone(); - if curr_line.len() + prefix_len < LEDGER_WIDTH { + let (key, mut value) = + curr_line.split_once(':').unwrap_or(("", &curr_line)); + // Truncate the key length to the declared maximum + let key = key.trim().chars().take(MAX_KEY_LEN - 1).collect::(); + // Trim value because we will insert spaces later + value = value.trim(); + if value.is_empty() { + value = "(none)" + } + if value.chars().count() < MAX_VALUE_LEN { // No need to split the line in this case - output[pos] = format!("{} | {}", i, curr_line); + output[pos] = format!("{} | {} : {}", i, key, value); pos += 1; } else { // Line is too long so split it up. Repeat the key on each line - let (mut key, mut value) = - curr_line.split_once(':').unwrap_or(("", &curr_line)); - key = key.trim(); - value = value.trim(); - if value.is_empty() { - value = "(none)" - } - - // First comput how many lines we will break the current one up into - let mut digits = 1; - let mut line_space; - let mut lines; - loop { - let prefix_len = prefix_len + 7 + 2 * digits + key.len(); - line_space = LEDGER_WIDTH - prefix_len; - lines = (value.len() + line_space - 1) / line_space; - if lines.to_string().len() <= digits { - break; - } else { - digits += 1; - } - } - - // Then break up this line according to the above plan output.remove(pos); - for (idx, part) in - value.chars().chunks(line_space).into_iter().enumerate() + let part_count = (value.chars().count() + MAX_VALUE_LEN - 2) + / (MAX_VALUE_LEN - 1); + for (idx, part) in value + .chars() + .chunks(MAX_VALUE_LEN - 1) + .into_iter() + .enumerate() { let line = format!( "{} | {} [{}/{}] : {}", i, key, idx + 1, - lines, + part_count, part.collect::(), ); output.insert(pos, line); @@ -817,6 +831,64 @@ pub async fn generate_test_vector<'a>( Ok(()) } +/// Convert decimal numbers into the format used by Ledger. Specifically remove +/// all insignificant zeros occuring after decimal point. +fn to_ledger_decimal(amount: &str) -> String { + if amount.contains('.') { + let mut amount = amount.trim_end_matches('0').to_string(); + if amount.ends_with('.') { + amount.push('0') + } + amount + } else { + amount.to_string() + ".0" + } +} + +/// A ProposalVote wrapper that prints the spending cap with Ledger decimal +/// formatting. +struct LedgerProposalVote<'a>(&'a StorageProposalVote); + +impl<'a> Display for LedgerProposalVote<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.0 { + StorageProposalVote::Yay(vote_type) => match vote_type { + VoteType::Default => write!(f, "yay"), + VoteType::PGFSteward => write!(f, "yay for PGF steward"), + VoteType::PGFPayment => { + write!(f, "yay for PGF payment proposal") + } + }, + + StorageProposalVote::Nay => write!(f, "nay"), + } + } +} + +/// A ProposalType wrapper that prints the hash of the contained WASM code if it +/// is present. +struct LedgerProposalType<'a>(&'a ProposalType, &'a Tx); + +impl<'a> Display for LedgerProposalType<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self.0 { + ProposalType::Default(None) => write!(f, "Default"), + ProposalType::Default(Some(hash)) => { + let extra = self + .1 + .get_section(hash) + .and_then(|x| Section::extra_data_sec(x.as_ref())) + .expect("unable to load vp code") + .code + .hash(); + write!(f, "{}", HEXLOWER.encode(&extra.0)) + } + ProposalType::PGFSteward(_) => write!(f, "PGF Steward"), + ProposalType::PGFPayment(_) => write!(f, "PGF Payment"), + } + } +} + /// Converts the given transaction to the form that is displayed on the Ledger /// device pub async fn to_ledger_vector<'a>( @@ -842,28 +914,23 @@ pub async fn to_ledger_vector<'a>( let change_commission_hash = query_wasm_code_hash(context, TX_CHANGE_COMMISSION_WASM).await?; let user_hash = query_wasm_code_hash(context, VP_USER_WASM).await?; + let unjail_validator_hash = + query_wasm_code_hash(context, TX_UNJAIL_VALIDATOR_WASM).await?; // To facilitate lookups of human-readable token names - let wallet = context.wallet().await; let tokens: HashMap = context .wallet() .await - .get_addresses_with_vp_type(AddressVpType::Token) + .get_addresses() .into_iter() - .map(|addr| { - let alias = match wallet.find_alias(&addr) { - Some(alias) => alias.to_string(), - None => addr.to_string(), - }; - (addr, alias) - }) + .map(|(alias, addr)| (addr, alias)) .collect(); let mut tv = LedgerVector { blob: HEXLOWER.encode(&tx.serialize_to_vec()), index: 0, valid: true, - name: "Custom 0".to_string(), + name: "Custom_0".to_string(), ..Default::default() }; @@ -889,7 +956,7 @@ pub async fn to_ledger_vector<'a>( .map_err(|err| { Error::from(EncodingError::Conversion(err.to_string())) })?; - tv.name = "Init Account 0".to_string(); + tv.name = "Init_Account_0".to_string(); let extra = tx .get_section(&init_account.vp_code_hash) @@ -902,15 +969,26 @@ pub async fn to_ledger_vector<'a>( } else { HEXLOWER.encode(&extra.0) }; - + tv.output.extend(vec![format!("Type : Init Account")]); + tv.output.extend( + init_account + .public_keys + .iter() + .map(|k| format!("Public key : {}", k)), + ); tv.output.extend(vec![ - format!("Type : Init Account"), - format!("Public key : {:?}", init_account.public_keys), + format!("Threshold : {}", init_account.threshold), format!("VP type : {}", vp_code), ]); + tv.output_expert.extend( + init_account + .public_keys + .iter() + .map(|k| format!("Public key : {}", k)), + ); tv.output_expert.extend(vec![ - format!("Public key : {:?}", init_account.public_keys), + format!("Threshold : {}", init_account.threshold), format!("VP type : {}", HEXLOWER.encode(&extra.0)), ]); } else if code_hash == init_validator_hash { @@ -922,7 +1000,7 @@ pub async fn to_ledger_vector<'a>( Error::from(EncodingError::Conversion(err.to_string())) })?; - tv.name = "Init Validator 0".to_string(); + tv.name = "Init_Validator_0".to_string(); let extra = tx .get_section(&init_validator.validator_vp_code_hash) @@ -936,10 +1014,18 @@ pub async fn to_ledger_vector<'a>( HEXLOWER.encode(&extra.0) }; + tv.output.extend(vec!["Type : Init Validator".to_string()]); + tv.output.extend( + init_validator + .account_keys + .iter() + .map(|k| format!("Account key : {}", k)), + ); tv.output.extend(vec![ - format!("Type : Init Validator"), - format!("Account key : {:?}", init_validator.account_keys), + format!("Threshold : {}", init_validator.threshold), format!("Consensus key : {}", init_validator.consensus_key), + format!("Ethereum cold key : {}", init_validator.eth_cold_key), + format!("Ethereum hot key : {}", init_validator.eth_hot_key), format!("Protocol key : {}", init_validator.protocol_key), format!("DKG key : {}", init_validator.dkg_key), format!("Commission rate : {}", init_validator.commission_rate), @@ -950,9 +1036,17 @@ pub async fn to_ledger_vector<'a>( format!("Validator VP type : {}", vp_code,), ]); + tv.output_expert.extend( + init_validator + .account_keys + .iter() + .map(|k| format!("Account key : {}", k)), + ); tv.output_expert.extend(vec![ - format!("Account key : {:?}", init_validator.account_keys), + format!("Threshold : {}", init_validator.threshold), format!("Consensus key : {}", init_validator.consensus_key), + format!("Ethereum cold key : {}", init_validator.eth_cold_key), + format!("Ethereum hot key : {}", init_validator.eth_hot_key), format!("Protocol key : {}", init_validator.protocol_key), format!("DKG key : {}", init_validator.dkg_key), format!("Commission rate : {}", init_validator.commission_rate), @@ -971,16 +1065,24 @@ pub async fn to_ledger_vector<'a>( Error::from(EncodingError::Conversion(err.to_string())) })?; - tv.name = "Init Proposal 0".to_string(); + tv.name = "Init_Proposal_0".to_string(); + + let extra = tx + .get_section(&init_proposal_data.content) + .and_then(|x| Section::extra_data_sec(x.as_ref())) + .expect("unable to load vp code") + .code + .hash(); - let init_proposal_data_id = init_proposal_data - .id - .as_ref() - .map(u64::to_string) - .unwrap_or_else(|| "(none)".to_string()); + tv.output.push("Type : Init proposal".to_string()); + if let Some(id) = init_proposal_data.id.as_ref() { + tv.output.push(format!("ID : {}", id)); + } tv.output.extend(vec![ - format!("Type : Init proposal"), - format!("ID : {}", init_proposal_data_id), + format!( + "Proposal type : {}", + LedgerProposalType(&init_proposal_data.r#type, tx) + ), format!("Author : {}", init_proposal_data.author), format!( "Voting start epoch : {}", @@ -991,12 +1093,17 @@ pub async fn to_ledger_vector<'a>( init_proposal_data.voting_end_epoch ), format!("Grace epoch : {}", init_proposal_data.grace_epoch), + format!("Content : {}", HEXLOWER.encode(&extra.0)), ]); - tv.output - .push(format!("Content: {}", init_proposal_data.content)); + if let Some(id) = init_proposal_data.id.as_ref() { + tv.output_expert.push(format!("ID : {}", id)); + } tv.output_expert.extend(vec![ - format!("ID : {}", init_proposal_data_id), + format!( + "Proposal type : {}", + LedgerProposalType(&init_proposal_data.r#type, tx) + ), format!("Author : {}", init_proposal_data.author), format!( "Voting start epoch : {}", @@ -1007,9 +1114,8 @@ pub async fn to_ledger_vector<'a>( init_proposal_data.voting_end_epoch ), format!("Grace epoch : {}", init_proposal_data.grace_epoch), + format!("Content : {}", HEXLOWER.encode(&extra.0)), ]); - tv.output - .push(format!("Content: {}", init_proposal_data.content)); } else if code_hash == vote_proposal_hash { let vote_proposal = VoteProposalData::try_from_slice( &tx.data() @@ -1019,26 +1125,26 @@ pub async fn to_ledger_vector<'a>( Error::from(EncodingError::Conversion(err.to_string())) })?; - tv.name = "Vote Proposal 0".to_string(); + tv.name = "Vote_Proposal_0".to_string(); tv.output.extend(vec![ format!("Type : Vote Proposal"), format!("ID : {}", vote_proposal.id), - format!("Vote : {}", vote_proposal.vote), + format!("Vote : {}", LedgerProposalVote(&vote_proposal.vote)), format!("Voter : {}", vote_proposal.voter), ]); for delegation in &vote_proposal.delegations { - tv.output.push(format!("Delegations : {}", delegation)); + tv.output.push(format!("Delegation : {}", delegation)); } tv.output_expert.extend(vec![ format!("ID : {}", vote_proposal.id), - format!("Vote : {}", vote_proposal.vote), + format!("Vote : {}", LedgerProposalVote(&vote_proposal.vote)), format!("Voter : {}", vote_proposal.voter), ]); for delegation in vote_proposal.delegations { tv.output_expert - .push(format!("Delegations : {}", delegation)); + .push(format!("Delegation : {}", delegation)); } } else if code_hash == reveal_pk_hash { let public_key = common::PublicKey::try_from_slice( @@ -1049,17 +1155,17 @@ pub async fn to_ledger_vector<'a>( Error::from(EncodingError::Conversion(err.to_string())) })?; - tv.name = "Init Account 0".to_string(); + tv.name = "Reveal_Pubkey_0".to_string(); tv.output.extend(vec![ - format!("Type : Reveal PK"), + format!("Type : Reveal Pubkey"), format!("Public key : {}", public_key), ]); tv.output_expert .extend(vec![format!("Public key : {}", public_key)]); } else if code_hash == update_account_hash { - let transfer = UpdateAccount::try_from_slice( + let update_account = UpdateAccount::try_from_slice( &tx.data() .ok_or_else(|| Error::Other("Invalid Data".to_string()))?, ) @@ -1067,9 +1173,9 @@ pub async fn to_ledger_vector<'a>( Error::from(EncodingError::Conversion(err.to_string())) })?; - tv.name = "Update VP 0".to_string(); + tv.name = "Update_VP_0".to_string(); - match &transfer.vp_code_hash { + match &update_account.vp_code_hash { Some(hash) => { let extra = tx .get_section(hash) @@ -1086,14 +1192,40 @@ pub async fn to_ledger_vector<'a>( }; tv.output.extend(vec![ format!("Type : Update VP"), - format!("Address : {}", transfer.addr), - format!("VP type : {}", vp_code), - ]); - - tv.output_expert.extend(vec![ - format!("Address : {}", transfer.addr), - format!("VP type : {}", HEXLOWER.encode(&extra.0)), + format!("Address : {}", update_account.addr), ]); + tv.output.extend( + update_account + .public_keys + .iter() + .map(|k| format!("Public key : {}", k)), + ); + if update_account.threshold.is_some() { + tv.output.extend(vec![format!( + "Threshold : {}", + update_account.threshold.unwrap() + )]) + } + tv.output.extend(vec![format!("VP type : {}", vp_code)]); + + tv.output_expert + .extend(vec![format!("Address : {}", update_account.addr)]); + tv.output_expert.extend( + update_account + .public_keys + .iter() + .map(|k| format!("Public key : {}", k)), + ); + if update_account.threshold.is_some() { + tv.output_expert.extend(vec![format!( + "Threshold : {}", + update_account.threshold.unwrap() + )]) + } + tv.output_expert.extend(vec![format!( + "VP type : {}", + HEXLOWER.encode(&extra.0) + )]); } None => (), }; @@ -1132,7 +1264,7 @@ pub async fn to_ledger_vector<'a>( None }; - tv.name = "Transfer 0".to_string(); + tv.name = "Transfer_0".to_string(); tv.output.push("Type : Transfer".to_string()); make_ledger_masp_endpoints( @@ -1161,7 +1293,7 @@ pub async fn to_ledger_vector<'a>( ) .map_err(|x| Error::from(EncodingError::Conversion(x.to_string())))?; - tv.name = "IBC 0".to_string(); + tv.name = "IBC_0".to_string(); tv.output.push("Type : IBC".to_string()); match MsgTransfer::try_from(any_msg.clone()) { @@ -1183,7 +1315,11 @@ pub async fn to_ledger_vector<'a>( ), format!( "Timeout timestamp : {}", - transfer.timeout_timestamp_on_b + transfer + .timeout_timestamp_on_b + .into_tm_time() + .map_or("(none)".to_string(), |time| time + .to_rfc3339()) ), ]); tv.output_expert.extend(vec![ @@ -1198,7 +1334,11 @@ pub async fn to_ledger_vector<'a>( ), format!( "Timeout timestamp : {}", - transfer.timeout_timestamp_on_b + transfer + .timeout_timestamp_on_b + .into_tm_time() + .map_or("(none)".to_string(), |time| time + .to_rfc3339()) ), ]); } @@ -1219,24 +1359,29 @@ pub async fn to_ledger_vector<'a>( Error::from(EncodingError::Conversion(err.to_string())) })?; - tv.name = "Bond 0".to_string(); + tv.name = "Bond_0".to_string(); - let bond_source = bond - .source - .as_ref() - .map(Address::to_string) - .unwrap_or_else(|| "(none)".to_string()); + tv.output.push("Type : Bond".to_string()); + if let Some(source) = bond.source.as_ref() { + tv.output.push(format!("Source : {}", source)); + } tv.output.extend(vec![ - format!("Type : Bond"), - format!("Source : {}", bond_source), format!("Validator : {}", bond.validator), - format!("Amount : {}", bond.amount.to_string_native()), + format!( + "Amount : NAM {}", + to_ledger_decimal(&bond.amount.to_string_native()) + ), ]); + if let Some(source) = bond.source.as_ref() { + tv.output_expert.push(format!("Source : {}", source)); + } tv.output_expert.extend(vec![ - format!("Source : {}", bond_source), format!("Validator : {}", bond.validator), - format!("Amount : {}", bond.amount.to_string_native()), + format!( + "Amount : NAM {}", + to_ledger_decimal(&bond.amount.to_string_native()) + ), ]); } else if code_hash == unbond_hash { let unbond = pos::Unbond::try_from_slice( @@ -1247,24 +1392,29 @@ pub async fn to_ledger_vector<'a>( Error::from(EncodingError::Conversion(err.to_string())) })?; - tv.name = "Unbond 0".to_string(); + tv.name = "Unbond_0".to_string(); - let unbond_source = unbond - .source - .as_ref() - .map(Address::to_string) - .unwrap_or_else(|| "(none)".to_string()); + tv.output.push("Type : Unbond".to_string()); + if let Some(source) = unbond.source.as_ref() { + tv.output.push(format!("Source : {}", source)); + } tv.output.extend(vec![ - format!("Code : Unbond"), - format!("Source : {}", unbond_source), format!("Validator : {}", unbond.validator), - format!("Amount : {}", unbond.amount.to_string_native()), + format!( + "Amount : NAM {}", + to_ledger_decimal(&unbond.amount.to_string_native()) + ), ]); + if let Some(source) = unbond.source.as_ref() { + tv.output_expert.push(format!("Source : {}", source)); + } tv.output_expert.extend(vec![ - format!("Source : {}", unbond_source), format!("Validator : {}", unbond.validator), - format!("Amount : {}", unbond.amount.to_string_native()), + format!( + "Amount : NAM {}", + to_ledger_decimal(&unbond.amount.to_string_native()) + ), ]); } else if code_hash == withdraw_hash { let withdraw = pos::Withdraw::try_from_slice( @@ -1275,23 +1425,20 @@ pub async fn to_ledger_vector<'a>( Error::from(EncodingError::Conversion(err.to_string())) })?; - tv.name = "Withdraw 0".to_string(); + tv.name = "Withdraw_0".to_string(); - let withdraw_source = withdraw - .source - .as_ref() - .map(Address::to_string) - .unwrap_or_else(|| "(none)".to_string()); - tv.output.extend(vec![ - format!("Type : Withdraw"), - format!("Source : {}", withdraw_source), - format!("Validator : {}", withdraw.validator), - ]); + tv.output.push("Type : Withdraw".to_string()); + if let Some(source) = withdraw.source.as_ref() { + tv.output.push(format!("Source : {}", source)); + } + tv.output + .push(format!("Validator : {}", withdraw.validator)); - tv.output_expert.extend(vec![ - format!("Source : {}", withdraw_source), - format!("Validator : {}", withdraw.validator), - ]); + if let Some(source) = withdraw.source.as_ref() { + tv.output_expert.push(format!("Source : {}", source)); + } + tv.output_expert + .push(format!("Validator : {}", withdraw.validator)); } else if code_hash == change_commission_hash { let commission_change = pos::CommissionChange::try_from_slice( &tx.data() @@ -1301,7 +1448,7 @@ pub async fn to_ledger_vector<'a>( Error::from(EncodingError::Conversion(err.to_string())) })?; - tv.name = "Change Commission 0".to_string(); + tv.name = "Change_Commission_0".to_string(); tv.output.extend(vec![ format!("Type : Change commission"), @@ -1313,6 +1460,26 @@ pub async fn to_ledger_vector<'a>( format!("New rate : {}", commission_change.new_rate), format!("Validator : {}", commission_change.validator), ]); + } else if code_hash == unjail_validator_hash { + let address = Address::try_from_slice( + &tx.data() + .ok_or_else(|| Error::Other("Invalid Data".to_string()))?, + ) + .map_err(|err| { + Error::from(EncodingError::Conversion(err.to_string())) + })?; + + tv.name = "Unjail_Validator_0".to_string(); + + tv.output.extend(vec![ + format!("Type : Unjail Validator"), + format!("Validator : {}", address), + ]); + + tv.output_expert.push(format!("Validator : {}", address)); + } else { + tv.name = "Custom_0".to_string(); + tv.output.push("Type : Custom".to_string()); } if let Some(wrapper) = tx.header.wrapper() { @@ -1325,21 +1492,21 @@ pub async fn to_ledger_vector<'a>( .await; tv.output_expert.extend(vec![ format!("Timestamp : {}", tx.header.timestamp.0), - format!("PK : {}", wrapper.pk), + format!("Pubkey : {}", wrapper.pk), format!("Epoch : {}", wrapper.epoch), format!("Gas limit : {}", gas_limit), - format!("Fee token : {}", gas_token), ]); if let Some(token) = tokens.get(&wrapper.fee.token) { tv.output_expert.push(format!( - "Fee amount per gas unit : {} {}", - token, fee_amount_per_gas_unit + "Fees/gas unit : {} {}", + token.to_uppercase(), + to_ledger_decimal(&fee_amount_per_gas_unit), )); } else { - tv.output_expert.push(format!( - "Fee amount per gas unit : {}", - fee_amount_per_gas_unit - )); + tv.output_expert.extend(vec![ + format!("Fee token : {}", gas_token), + format!("Fees/gas unit : {}", fee_amount_per_gas_unit), + ]); } } diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index 2fbeb449e2..f1a85ebca8 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -748,13 +748,8 @@ pub async fn build_unjail_validator<'a>( validator, Some(pipeline_epoch), ) - .await? - .ok_or_else(|| { - Error::from(TxError::Other( - "Validator state should be defined.".to_string(), - )) - })?; - if validator_state_at_pipeline != ValidatorState::Jailed { + .await?; + if validator_state_at_pipeline != Some(ValidatorState::Jailed) { edisplay_line!( context.io(), "The given validator address {} is not jailed at the pipeline \