use core::fmt;
use paralock_core::protocol::{ParalockError, ParalockErrorCode, Swap};
use crate::evm::EvmError;
use crate::store::StoreError;
use crate::zcash::NodeError;
pub type Result<T, E = Error> = core::result::Result<T, E>;
#[derive(thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Protocol(#[from] ParalockError),
#[error("zcash node: {0}")]
Zcash(#[from] NodeError),
#[error("evm: {0}")]
Evm(#[from] EvmError),
#[error("store: {0}")]
Store(#[from] StoreError),
#[error(transparent)]
Core(#[from] paralock_core::Error),
#[error("{source}")]
SwapFailed {
swap: Box<Swap>,
#[source]
source: Box<Error>,
},
#[error("{0}")]
Internal(String),
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Protocol(e) => f.debug_tuple("Protocol").field(e).finish(),
Self::Zcash(e) => f.debug_tuple("Zcash").field(e).finish(),
Self::Evm(e) => f.debug_tuple("Evm").field(e).finish(),
Self::Store(e) => f.debug_tuple("Store").field(e).finish(),
Self::Core(e) => f.debug_tuple("Core").field(e).finish(),
Self::SwapFailed { swap, source } => {
f.debug_struct("SwapFailed").field("swap", &SwapSummary(swap)).field("source", source).finish()
}
Self::Internal(message) => f.debug_tuple("Internal").field(message).finish(),
}
}
}
pub(crate) struct SwapSummary<'a>(pub &'a Swap);
impl fmt::Debug for SwapSummary<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = self.0;
f.debug_struct("Swap")
.field("id", &s.id)
.field("mode", &s.mode)
.field("status", &s.status)
.field("chain", &s.evm.chain)
.field("zat", &s.zcash.amount_zat)
.field("usd_amount", &s.evm.amount)
.field("lock_address", &s.zcash.htlc_address)
.field("refund_height", &s.zcash.refund_height)
.field("funding_txid", &s.zcash.funding_txid)
.field("claim_txid", &s.zcash.claim_txid)
.field("refund_txid", &s.zcash.refund_txid)
.field("evm_expiry", &s.evm.expiry)
.field("lock_tx", &s.evm.lock_tx)
.field("claim_tx", &s.evm.claim_tx)
.field("refund_tx", &s.evm.refund_tx)
.finish_non_exhaustive()
}
}
impl Error {
pub fn protocol(code: ParalockErrorCode, message: impl Into<String>) -> Self {
Self::Protocol(ParalockError::new(code, message))
}
pub fn invalid_request(message: impl Into<String>) -> Self {
Self::protocol(ParalockErrorCode::InvalidRequest, message)
}
pub fn code(&self) -> ParalockErrorCode {
match self {
Self::Protocol(e) => e.code,
Self::Zcash(_) | Self::Evm(_) => ParalockErrorCode::Unavailable,
Self::Store(_) | Self::Internal(_) => ParalockErrorCode::Internal,
Self::Core(e) => core_code(e),
Self::SwapFailed { source, .. } => source.code(),
}
}
pub fn is(&self, code: ParalockErrorCode) -> bool {
self.code() == code
}
pub fn failed_swap(&self) -> Option<&Swap> {
match self {
Self::SwapFailed { swap, .. } => Some(swap),
_ => None,
}
}
pub fn to_paralock_error(&self) -> ParalockError {
match self {
Self::Protocol(e) => e.clone(),
Self::SwapFailed { source, .. } => source.to_paralock_error(),
other => ParalockError::new(other.code(), other.to_string()),
}
}
}
fn core_code(e: ¶lock_core::Error) -> ParalockErrorCode {
use paralock_core::zcash::Error as Z;
match e {
paralock_core::Error::Protocol(p) => p.code,
paralock_core::Error::Zcash(
Z::InsufficientFunds { .. } | Z::NoUtxos | Z::Dust(_) | Z::InvalidAddress(_) | Z::WrongNetwork { .. },
) => ParalockErrorCode::InvalidRequest,
_ => ParalockErrorCode::Internal,
}
}
impl From<paralock_core::zcash::Error> for Error {
fn from(e: paralock_core::zcash::Error) -> Self {
Self::Core(e.into())
}
}
impl From<paralock_core::adaptor::Error> for Error {
fn from(e: paralock_core::adaptor::Error) -> Self {
Self::Core(e.into())
}
}
impl From<paralock_core::evm::Error> for Error {
fn from(e: paralock_core::evm::Error) -> Self {
Self::Core(e.into())
}
}
impl From<paralock_core::keys::KeyError> for Error {
fn from(e: paralock_core::keys::KeyError) -> Self {
Self::Core(e.into())
}
}
impl From<paralock_core::encoding::EncodingError> for Error {
fn from(e: paralock_core::encoding::EncodingError) -> Self {
Self::Core(e.into())
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Self::Internal(format!("JSON: {e}"))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use super::*;
#[test]
fn codes() {
assert_eq!(Error::invalid_request("x").code(), ParalockErrorCode::InvalidRequest);
let node = NodeError::Transport { method: "getblockcount".into(), message: "timed out".into() };
assert_eq!(Error::from(node).code(), ParalockErrorCode::Unavailable);
let funds = paralock_core::zcash::Error::InsufficientFunds { have: 1, need: 2 };
assert_eq!(Error::from(funds).code(), ParalockErrorCode::InvalidRequest);
let inner = Error::protocol(ParalockErrorCode::Timeout, "slow");
let swap_failed = Error::SwapFailed { swap: Box::new(crate::testutil::swap()), source: Box::new(inner) };
assert_eq!(swap_failed.code(), ParalockErrorCode::Timeout);
assert_eq!(swap_failed.to_string(), "slow");
assert!(swap_failed.failed_swap().is_some());
assert_eq!(swap_failed.to_paralock_error().message, "slow");
}
}