use std::error;
use std::fmt::{self, Debug};
use std::io;
use std::str::FromStr;
use strict_encoding::{StrictDecode, StrictEncode};
use thiserror::Error;
use crate::consensus::{self, deserialize, serialize, CanonicalBytes, Decodable, Encodable};
use crate::transaction::{Buyable, Cancelable, Fundable, Lockable, Punishable, Refundable};
#[derive(
Debug,
Clone,
Copy,
Hash,
PartialEq,
Eq,
Parser,
Display,
Serialize,
Deserialize,
StrictEncode,
StrictDecode,
)]
#[display(Debug)]
pub enum Blockchain {
Bitcoin,
Monero,
}
impl FromStr for Blockchain {
type Err = consensus::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Bitcoin" | "bitcoin" | "btc" | "BTC" => Ok(Blockchain::Bitcoin),
"Monero" | "monero" | "xmr" | "XMR" => Ok(Blockchain::Monero),
_ => Err(consensus::Error::UnknownType),
}
}
}
impl Decodable for Blockchain {
fn consensus_decode<D: io::Read>(d: &mut D) -> Result<Self, consensus::Error> {
match Decodable::consensus_decode(d)? {
0x80000000u32 => Ok(Blockchain::Bitcoin),
0x80000080u32 => Ok(Blockchain::Monero),
_ => Err(consensus::Error::UnknownType),
}
}
}
impl Encodable for Blockchain {
fn consensus_encode<W: io::Write>(&self, writer: &mut W) -> Result<usize, io::Error> {
match self {
Blockchain::Bitcoin => 0x80000000u32.consensus_encode(writer),
Blockchain::Monero => 0x80000080u32.consensus_encode(writer),
}
}
}
pub trait Transactions {
type Addr;
type Amt;
type Tx;
type Px;
type Out: Eq;
type Ti;
type Ms;
type Pk;
type Si;
type Funding: Fundable<Self::Tx, Self::Out, Self::Addr, Self::Pk>;
type Lock: Lockable<
Self::Addr,
Self::Tx,
Self::Px,
Self::Out,
Self::Amt,
Self::Ti,
Self::Ms,
Self::Pk,
Self::Si,
>;
type Buy: Buyable<
Self::Addr,
Self::Tx,
Self::Px,
Self::Out,
Self::Amt,
Self::Ti,
Self::Ms,
Self::Pk,
Self::Si,
>;
type Cancel: Cancelable<
Self::Addr,
Self::Tx,
Self::Px,
Self::Out,
Self::Amt,
Self::Ti,
Self::Ms,
Self::Pk,
Self::Si,
>;
type Refund: Refundable<
Self::Addr,
Self::Tx,
Self::Px,
Self::Out,
Self::Amt,
Self::Ti,
Self::Ms,
Self::Pk,
Self::Si,
>;
type Punish: Punishable<
Self::Addr,
Self::Tx,
Self::Px,
Self::Out,
Self::Amt,
Self::Ti,
Self::Ms,
Self::Pk,
Self::Si,
>;
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub enum FeeStrategy<T> {
Fixed(T),
#[cfg(feature = "fee_range")]
#[cfg_attr(docsrs, doc(cfg(feature = "fee_range")))]
Range { min_inc: T, max_inc: T },
}
impl<T> FeeStrategy<T>
where
T: PartialEq + PartialOrd,
{
pub fn check(&self, value: &T) -> bool {
match self {
Self::Fixed(fee_strat) => value == fee_strat,
#[cfg(feature = "fee_range")]
Self::Range { min_inc, max_inc } => value >= min_inc && value <= max_inc,
}
}
}
impl<T> FromStr for FeeStrategy<T>
where
T: FromStr,
{
type Err = consensus::Error;
#[allow(unused_mut)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts: Vec<&str> = s.split('-').collect();
match parts.len() {
1 => match s.parse::<T>() {
Ok(x) => Ok(Self::Fixed(x)),
Err(_) => Err(consensus::Error::ParseFailed("Failed parsing FeeStrategy")),
},
#[cfg(feature = "fee_range")]
2 => {
let max_inc = parts
.pop()
.expect("lenght is checked")
.parse::<T>()
.map_err(|_| consensus::Error::ParseFailed("Failed parsing FeeStrategy"))?;
let min_inc = parts
.pop()
.expect("lenght is checked")
.parse::<T>()
.map_err(|_| consensus::Error::ParseFailed("Failed parsing FeeStrategy"))?;
Ok(Self::Range { min_inc, max_inc })
}
_ => Err(consensus::Error::ParseFailed("Failed parsing FeeStrategy")),
}
}
}
impl<T> fmt::Display for FeeStrategy<T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
FeeStrategy::Fixed(t) => write!(f, "{}", t),
#[cfg(feature = "fee_range")]
FeeStrategy::Range { min_inc, max_inc } => {
write!(f, "{}-{}", min_inc, max_inc)
}
}
}
}
impl<T> Encodable for FeeStrategy<T>
where
T: CanonicalBytes,
{
fn consensus_encode<W: io::Write>(&self, writer: &mut W) -> Result<usize, io::Error> {
match self {
FeeStrategy::Fixed(t) => {
0x01u8.consensus_encode(writer)?;
Ok(t.as_canonical_bytes().consensus_encode(writer)? + 1)
}
#[cfg(feature = "fee_range")]
FeeStrategy::Range { min_inc, max_inc } => {
let mut len = 0x02u8.consensus_encode(writer)?;
len += min_inc.as_canonical_bytes().consensus_encode(writer)?;
Ok(len + max_inc.as_canonical_bytes().consensus_encode(writer)?)
}
}
}
}
impl<T> Decodable for FeeStrategy<T>
where
T: CanonicalBytes,
{
fn consensus_decode<D: io::Read>(d: &mut D) -> Result<Self, consensus::Error> {
match Decodable::consensus_decode(d)? {
0x01u8 => Ok(FeeStrategy::Fixed(T::from_canonical_bytes(
unwrap_vec_ref!(d).as_ref(),
)?)),
#[cfg(feature = "fee_range")]
0x02u8 => {
let min_inc = T::from_canonical_bytes(unwrap_vec_ref!(d).as_ref())?;
let max_inc = T::from_canonical_bytes(unwrap_vec_ref!(d).as_ref())?;
Ok(FeeStrategy::Range { min_inc, max_inc })
}
_ => Err(consensus::Error::UnknownType),
}
}
}
impl<T> CanonicalBytes for FeeStrategy<T>
where
T: CanonicalBytes,
{
fn as_canonical_bytes(&self) -> Vec<u8> {
serialize(self)
}
fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, consensus::Error>
where
Self: Sized,
{
deserialize(bytes)
}
}
impl_strict_encoding!(FeeStrategy<T>, T: CanonicalBytes);
#[derive(Error, Debug)]
pub enum FeeStrategyError {
#[error("Missing metadata inputs to retreive available amount")]
MissingInputsMetadata,
#[error("Fee amount is too low")]
AmountOfFeeTooLow,
#[error("Fee amount is too high")]
AmountOfFeeTooHigh,
#[error("Not enough assets to cover the fees")]
NotEnoughAssets,
#[error("Other: {0}")]
Other(Box<dyn error::Error + Sync + Send>),
}
impl FeeStrategyError {
pub fn new<E>(error: E) -> Self
where
E: Into<Box<dyn error::Error + Send + Sync>>,
{
Self::Other(error.into())
}
pub fn into_inner(self) -> Option<Box<dyn error::Error + Sync + Send>> {
match self {
Self::Other(error) => Some(error),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, Display, Serialize, Deserialize)]
#[display(Debug)]
pub enum FeePriority {
Low,
High,
}
impl Decodable for FeePriority {
fn consensus_decode<D: io::Read>(d: &mut D) -> Result<Self, consensus::Error> {
match Decodable::consensus_decode(d)? {
0x01u8 => Ok(FeePriority::Low),
0x02u8 => Ok(FeePriority::High),
_ => Err(consensus::Error::UnknownType),
}
}
}
impl Encodable for FeePriority {
fn consensus_encode<W: io::Write>(&self, writer: &mut W) -> Result<usize, io::Error> {
match self {
FeePriority::Low => 0x01u8.consensus_encode(writer),
FeePriority::High => 0x02u8.consensus_encode(writer),
}
}
}
impl FromStr for FeePriority {
type Err = consensus::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Low" | "low" => Ok(FeePriority::Low),
"High" | "high" => Ok(FeePriority::High),
_ => Err(consensus::Error::UnknownType),
}
}
}
pub trait Fee {
type FeeUnit;
type Amount;
fn set_fee(
&mut self,
strategy: &FeeStrategy<Self::FeeUnit>,
politic: FeePriority,
) -> Result<Self::Amount, FeeStrategyError>;
fn validate_fee(&self, strategy: &FeeStrategy<Self::FeeUnit>)
-> Result<bool, FeeStrategyError>;
}
impl FromStr for Network {
type Err = consensus::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Mainnet" | "mainnet" => Ok(Network::Mainnet),
"Testnet" | "testnet" => Ok(Network::Testnet),
"Local" | "local" => Ok(Network::Local),
_ => Err(consensus::Error::UnknownType),
}
}
}
#[derive(
Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug, Display, Serialize, Deserialize,
)]
#[display(Debug)]
pub enum Network {
Mainnet,
Testnet,
Local,
}
impl Encodable for Network {
fn consensus_encode<W: io::Write>(&self, writer: &mut W) -> Result<usize, io::Error> {
match self {
Network::Mainnet => 0x01u8.consensus_encode(writer),
Network::Testnet => 0x02u8.consensus_encode(writer),
Network::Local => 0x03u8.consensus_encode(writer),
}
}
}
impl Decodable for Network {
fn consensus_decode<D: io::Read>(d: &mut D) -> Result<Self, consensus::Error> {
match Decodable::consensus_decode(d)? {
0x01u8 => Ok(Network::Mainnet),
0x02u8 => Ok(Network::Testnet),
0x03u8 => Ok(Network::Local),
_ => Err(consensus::Error::UnknownType),
}
}
}
impl_strict_encoding!(Network);
#[cfg(test)]
mod tests {
use super::*;
use crate::bitcoin::fee::SatPerKvB;
#[test]
fn parse_fee_politic() {
for s in ["High", "high", "Low", "low"].iter() {
let parse = FeePriority::from_str(s);
assert!(parse.is_ok());
}
}
#[test]
fn parse_network() {
for s in ["Mainnet", "mainnet", "Testnet", "testnet", "Local", "local"].iter() {
let parse = Network::from_str(s);
assert!(parse.is_ok());
}
}
#[test]
fn bitcoin_network_conversion() {
assert_eq!(Network::from(bitcoin::Network::Bitcoin), Network::Mainnet);
assert_eq!(Network::from(bitcoin::Network::Testnet), Network::Testnet);
assert_eq!(Network::from(bitcoin::Network::Signet), Network::Testnet);
assert_eq!(Network::from(bitcoin::Network::Regtest), Network::Local);
assert_eq!(
bitcoin::Network::from(Network::Mainnet),
bitcoin::Network::Bitcoin
);
assert_eq!(
Into::<bitcoin::Network>::into(Network::Mainnet),
bitcoin::Network::Bitcoin,
);
assert_eq!(
bitcoin::Network::from(Network::Testnet),
bitcoin::Network::Testnet
);
assert_eq!(
Into::<bitcoin::Network>::into(Network::Testnet),
bitcoin::Network::Testnet,
);
assert_eq!(
bitcoin::Network::from(Network::Local),
bitcoin::Network::Regtest
);
assert_eq!(
Into::<bitcoin::Network>::into(Network::Local),
bitcoin::Network::Regtest,
);
}
#[test]
fn monero_network_conversion() {
assert_eq!(
monero::Network::from(Network::Mainnet),
monero::Network::Mainnet
);
assert_eq!(
monero::Network::from(Network::Testnet),
monero::Network::Stagenet
);
assert_eq!(
monero::Network::from(Network::Local),
monero::Network::Mainnet
);
}
#[test]
fn fee_strategy_display() {
let strategy = FeeStrategy::Fixed(SatPerKvB::from_sat(100));
assert_eq!(&format!("{}", strategy), "100 satoshi/kvB");
#[cfg(feature = "fee_range")]
{
let strategy = FeeStrategy::Range {
min_inc: SatPerKvB::from_sat(50),
max_inc: SatPerKvB::from_sat(150),
};
assert_eq!(&format!("{}", strategy), "50 satoshi/kvB-150 satoshi/kvB")
}
}
#[test]
fn fee_strategy_parse() {
let strings = [
"100 satoshi/kvB",
#[cfg(feature = "fee_range")]
"50 satoshi/kvB-150 satoshi/kvB",
];
let res = [
FeeStrategy::Fixed(SatPerKvB::from_sat(100)),
#[cfg(feature = "fee_range")]
FeeStrategy::Range {
min_inc: SatPerKvB::from_sat(50),
max_inc: SatPerKvB::from_sat(150),
},
];
for (s, r) in strings.iter().zip(res) {
let strategy = FeeStrategy::<SatPerKvB>::from_str(s);
assert!(strategy.is_ok());
assert_eq!(strategy.unwrap(), r);
}
}
#[test]
fn fee_strategy_to_str_from_str() {
let strats = [
FeeStrategy::Fixed(SatPerKvB::from_sat(1)),
#[cfg(feature = "fee_range")]
FeeStrategy::Range {
min_inc: SatPerKvB::from_sat(1),
max_inc: SatPerKvB::from_sat(7),
},
];
for strat in strats.iter() {
assert_eq!(
FeeStrategy::<SatPerKvB>::from_str(&strat.to_string()).unwrap(),
*strat
)
}
}
#[test]
#[cfg(feature = "fee_range")]
fn fee_strategy_check_range() {
let strategy = FeeStrategy::Range {
min_inc: SatPerKvB::from_sat(50),
max_inc: SatPerKvB::from_sat(150),
};
assert!(!strategy.check(&SatPerKvB::from_sat(49)));
assert!(strategy.check(&SatPerKvB::from_sat(50)));
assert!(strategy.check(&SatPerKvB::from_sat(150)));
assert!(!strategy.check(&SatPerKvB::from_sat(151)));
}
}