use crate::error::{ArrayStringError, ByteDecodeError, CosmosGrpcError, SdkErrorCode};
use crate::Coin;
use bytes::BytesMut;
use cosmos_sdk_proto::cosmos::base::abci::v1beta1::TxResponse;
use prost::{DecodeError, Message};
use prost_types::Any;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::time::Duration;
use std::{str, usize};
pub fn hex_str_to_bytes(s: &str) -> Result<Vec<u8>, ByteDecodeError> {
let s = match s.strip_prefix("0x") {
Some(v) => v,
None => s,
};
s.as_bytes()
.chunks(2)
.map(|ch| {
str::from_utf8(ch)
.map_err(ByteDecodeError::DecodeError)
.and_then(|res| u8::from_str_radix(res, 16).map_err(ByteDecodeError::ParseError))
})
.collect()
}
pub fn bytes_to_hex_str(bytes: &[u8]) -> String {
bytes
.iter()
.map(|b| format!("{b:0>2x?}"))
.fold(String::new(), |acc, x| acc + &x)
}
#[derive(PartialEq, Eq, Copy, Clone, Hash, Deserialize, Serialize)]
pub struct ArrayString {
chars: [Option<char>; ArrayString::MAX_LEN],
used: usize,
}
impl ArrayString {
const MAX_LEN: usize = 32;
pub fn new(input: &str) -> Result<Self, ArrayStringError> {
if input.len() > ArrayString::MAX_LEN {
Err(ArrayStringError::TooLong)
} else {
let mut ret: [Option<char>; ArrayString::MAX_LEN] = [None; ArrayString::MAX_LEN];
let mut counter = 0;
for char in input.chars() {
ret[counter] = Some(char);
counter += 1;
}
Ok(ArrayString {
chars: ret,
used: counter,
})
}
}
}
impl Display for ArrayString {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let mut str = String::new();
for c in self.chars.iter() {
if let Some(v) = c {
str.push(*v)
} else {
break;
}
}
write!(f, "{str}")
}
}
pub fn contains_non_hex_chars(input: &str) -> bool {
for char in input.chars() {
if !char.is_ascii_hexdigit() {
return true;
}
}
false
}
#[derive(PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Debug)]
pub enum FeeInfo {
InsufficientFees { min_fees: Vec<Coin> },
InsufficientGas { amount: u64 },
}
pub fn determine_min_fees_and_gas(input: &TxResponse) -> Option<FeeInfo> {
if input.gas_used > input.gas_wanted {
return Some(FeeInfo::InsufficientGas {
amount: input.gas_used as u64,
});
}
if input.codespace == "sdk" {
if let Some(err) = SdkErrorCode::from_code(input.code) {
if err == SdkErrorCode::ErrInsufficientFee {
let parts = input.raw_log.split(':').nth(2);
if let Some(amounts) = parts {
let mut coins = Vec::new();
for item in amounts.split(',') {
if let Ok(coin) = item.parse() {
coins.push(coin);
}
}
Some(FeeInfo::InsufficientFees { min_fees: coins })
} else {
error!("Failed parsing insufficient fee error, probably changed gRPC error message response");
None
}
} else {
None
}
} else {
None
}
} else {
None
}
}
pub fn check_for_sdk_error(input: &TxResponse) -> Result<(), CosmosGrpcError> {
if let Some(v) = determine_min_fees_and_gas(input) {
return Err(CosmosGrpcError::InsufficientFees { fee_info: v });
}
if input.codespace == "sdk" {
if let Some(e) = SdkErrorCode::from_code(input.code) {
return Err(CosmosGrpcError::TransactionFailed {
tx: input.clone(),
time: Duration::from_secs(0),
sdk_error: Some(e),
});
}
}
Ok(())
}
pub fn encode_any(input: impl prost::Message, type_url: impl Into<String>) -> Any {
let mut value = Vec::new();
input.encode(&mut value).unwrap();
Any {
type_url: type_url.into(),
value,
}
}
pub fn decode_any<T: Message + Default>(any: Any) -> Result<T, DecodeError> {
let bytes = any.value;
decode_bytes(bytes)
}
pub fn decode_bytes<T: Message + Default>(bytes: Vec<u8>) -> Result<T, DecodeError> {
let mut buf = BytesMut::with_capacity(bytes.len());
buf.extend_from_slice(&bytes);
T::decode(buf)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_determine_fees() {
let below_min_fees_tx_response = TxResponse {
height: 0,
txhash: "3B07E4A68F2260717E45F4469CC197DBC2637858C33B4790B83F4AE9FC058570".to_string(),
codespace: "sdk".to_string(),
code: 13,
data: String::new(),
raw_log: "insufficient fees; got: 1gravity0xD50c0953a99325d01cca655E57070F1be4983b6b required: 50000ualtg,250000ufootoken: insufficient fee".to_string(),
logs: Vec::new(),
info: String::new(),
gas_used: 0,
gas_wanted: 0,
tx: None,
timestamp: String::new(),
events: Vec::new(),
};
let correct_output = Some(FeeInfo::InsufficientFees {
min_fees: vec![
Coin {
denom: "ualtg".to_string(),
amount: 50000u64.into(),
},
Coin {
denom: "ufootoken".to_string(),
amount: 250000u64.into(),
},
],
});
assert_eq!(
determine_min_fees_and_gas(&below_min_fees_tx_response),
correct_output
);
}
}