use super::{Denom, SCHEMA};
use crate::{
error::{Error, ErrorKind},
map,
prelude::*,
Map,
};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use sha2::{Digest, Sha256};
use std::{
convert::TryFrom,
fmt::{self, Display},
};
use stdtx::{Address, Decimal};
pub type Hash = [u8; 20];
#[derive(Clone, Debug)]
pub struct MsgAggregateExchangeRateVote {
pub exchange_rates: ExchangeRates,
pub salt: String,
pub feeder: Address,
pub validator: Address,
}
impl MsgAggregateExchangeRateVote {
pub fn random_salt() -> String {
String::from_utf8(thread_rng().sample_iter(&Alphanumeric).take(4).collect())
.expect("UTF-8 error")
}
pub fn to_stdtx_msg(&self) -> eyre::Result<stdtx::amino::Msg> {
Ok(
stdtx::amino::msg::Builder::new(&SCHEMA, "oracle/MsgAggregateExchangeRateVote")?
.string("exchange_rates", self.exchange_rates.to_string())?
.string("salt", &self.salt)?
.acc_address("feeder", self.feeder)?
.val_address("validator", self.validator)?
.to_msg(),
)
}
pub fn prevote(&self) -> MsgAggregateExchangeRatePrevote {
MsgAggregateExchangeRatePrevote {
hash: self.generate_vote_hash(),
feeder: self.feeder,
validator: self.validator,
}
}
fn generate_vote_hash(&self) -> Hash {
let data = format!(
"{}:{}:{}",
self.salt,
self.exchange_rates.to_string(),
self.validator.to_bech32("terravaloper"),
);
let digest = Sha256::digest(data.as_bytes());
Hash::try_from(&digest[..20]).unwrap()
}
}
#[derive(Clone, Debug)]
pub struct MsgAggregateExchangeRatePrevote {
pub hash: Hash,
pub feeder: Address,
pub validator: Address,
}
impl MsgAggregateExchangeRatePrevote {
pub fn to_stdtx_msg(&self) -> eyre::Result<stdtx::amino::Msg> {
Ok(
stdtx::amino::msg::Builder::new(&SCHEMA, "oracle/MsgAggregateExchangeRatePrevote")?
.bytes("hash", self.hash.as_ref())?
.acc_address("feeder", self.feeder)?
.val_address("validator", self.validator)?
.to_msg(),
)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ExchangeRates(Map<Denom, Decimal>);
impl ExchangeRates {
pub fn new() -> Self {
Self::default()
}
pub fn from_exchange_rates<'a, I>(iter: I) -> Result<Self, Error>
where
I: Iterator<Item = &'a (Denom, Decimal)>,
{
let mut exchange_rates = ExchangeRates::new();
for &(denom, rate) in iter {
exchange_rates.add(denom, rate)?;
}
Ok(exchange_rates)
}
pub fn add(&mut self, denom: Denom, rate: Decimal) -> Result<(), Error> {
let duplicate = self.0.insert(denom, rate).is_some();
ensure!(
!duplicate,
ErrorKind::Currency,
"duplicate exchange rate for denom: {}",
denom
);
Ok(())
}
pub fn iter(&self) -> map::Iter<'_, Denom, Decimal> {
self.0.iter()
}
}
impl Display for ExchangeRates {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, (denom, rate)) in self.0.iter().enumerate() {
write!(f, "{}{}", rate, denom)?;
if i < self.0.len() - 1 {
write!(f, ",")?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{Denom, ExchangeRates};
#[test]
fn exchange_rate_to_string() {
let exchange_rates = ExchangeRates::from_exchange_rates(
[
(Denom::Uusd, "1".parse().unwrap()),
(Denom::Usdr, "1".parse().unwrap()),
(Denom::Umnt, "888".parse().unwrap()),
(Denom::Ukrw, "362".parse().unwrap()),
]
.iter(),
)
.unwrap();
let serialized_rates = exchange_rates.to_string();
assert_eq!(
&serialized_rates,
"362.000000000000000000ukrw,\
888.000000000000000000umnt,\
1.000000000000000000usdr,\
1.000000000000000000uusd"
);
}
}