Skip to main content

miden_usdcx/xreserve/encoding/
error.rs

1//! The error type every encoding routine returns.
2//!
3//! One enum covers the whole encoding surface so a caller handles failures from the amount reducer,
4//! the intent parser, and the codecs uniformly.
5
6use core::fmt;
7
8use miden_protocol::utils::serde::DeserializationError;
9use miden_standards::interop::eth::EthAmountError;
10
11use super::deposit_intent::DepositIntentField;
12use super::domain::CircleDomain;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum EncodingError {
16    LimbOutOfField,
17    /// A u32-LE-packed felt limb exceeds `u32::MAX` (the `packed_felts_to_bytes32` guard on the
18    /// burn-note item decode, distinct from `LimbOutOfField`'s 8-byte/felt `>= p` Word-packing
19    /// check).
20    LimbNotU32,
21    /// A deposit amount too wide to reduce to an asset amount at all: the quotient does not fit a
22    /// `u64`. Every uint256 above 2^128 lands here, whatever the scale exponent.
23    AmountTooLarge,
24    /// The reduced amount fits a `u64` but exceeds `AssetAmount::MAX`.
25    AmountOverCap,
26    ScaleExpTooLarge,
27    BadMagic,
28    BadVersion,
29    ZeroField {
30        field: DepositIntentField,
31    },
32    TruncatedHeader,
33    LengthMismatch,
34    HookDataTooLarge,
35    /// A field the mint note must carry as a single `AssetAmount` felt holds a wire value outside
36    /// that range, so the note cannot express it. The faucet rebuilds the signed message from what
37    /// the note carries, so an unrepresentable field makes the deposit unmintable rather than
38    /// merely rejected on-chain.
39    FieldNotAssetAmount {
40        field: DepositIntentField,
41    },
42    /// The intent's `remoteToken` is not this faucet's account id.
43    RemoteTokenMismatch,
44    /// The intent's `remoteDomain` is not the domain the consuming faucet has configured. The
45    /// faucet writes its own configured domain into the message it rebuilds, so a divergent one
46    /// changes the digest and the attestation stops verifying.
47    RemoteDomainMismatch {
48        expected: CircleDomain,
49        actual: CircleDomain,
50    },
51    AccountIdOutOfRange,
52    NonCanonicalAccountId,
53    BurnItemsMalformed,
54    JsonSchema(String),
55    BinaryMagic,
56    BinaryLength,
57}
58
59impl fmt::Display for EncodingError {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        match self {
62            Self::LimbOutOfField => write!(f, "a u64 limb is not a valid field element"),
63            Self::LimbNotU32 => write!(f, "packed felt exceeds u32 range"),
64            Self::AmountTooLarge => write!(f, "post-scale quotient does not fit a u64"),
65            Self::AmountOverCap => {
66                write!(f, "post-scale quotient exceeds the asset amount maximum")
67            },
68            Self::ScaleExpTooLarge => write!(f, "scale exponent exceeds 18"),
69            Self::BadMagic => write!(f, "deposit intent magic mismatch"),
70            Self::BadVersion => write!(f, "deposit intent version mismatch"),
71            Self::ZeroField { field } => write!(f, "deposit intent field {field:?} is zero"),
72            Self::TruncatedHeader => write!(f, "deposit intent header is shorter than 240 bytes"),
73            Self::LengthMismatch => write!(f, "deposit intent length relation violated"),
74            Self::HookDataTooLarge => write!(f, "hook data exceeds the note storage felt bound"),
75            Self::FieldNotAssetAmount { field } => {
76                write!(f, "deposit intent field {field:?} is not a valid asset amount")
77            },
78            Self::RemoteTokenMismatch => {
79                write!(f, "deposit intent remote token is not the faucet account id")
80            },
81            Self::RemoteDomainMismatch { expected, actual } => {
82                write!(
83                    f,
84                    "deposit intent remote domain {actual} is not the faucet's configured domain {expected}"
85                )
86            },
87            Self::AccountIdOutOfRange => {
88                // The right-aligned (Agglayer-mirroring) layout: the account id region is the
89                // 16 bytes `bytes[16..32]` (prefix u64 BE + suffix u64 BE) behind a 16-byte zero
90                // pad — the message names the 16-byte region of the shipped layout.
91                write!(f, "bytes set outside the 16-byte account id region")
92            },
93            Self::NonCanonicalAccountId => {
94                write!(f, "bytes do not decode to a canonical account id")
95            },
96            Self::BurnItemsMalformed => write!(f, "burn note items have the wrong length or shape"),
97            Self::JsonSchema(msg) => write!(f, "circle json does not match the schema: {msg}"),
98            Self::BinaryMagic => write!(f, "circle binary decoder magic mismatch"),
99            Self::BinaryLength => write!(f, "circle binary length reconciliation failed"),
100        }
101    }
102}
103
104impl core::error::Error for EncodingError {}
105
106impl From<EncodingError> for DeserializationError {
107    /// Carries the reason across the protocol's deserialization boundary. The specific variant is
108    /// preserved by the typed entry points (`TryFrom<&[u8]>`), which is where callers that branch
109    /// on the failure read it; this spelling exists so the codecs can implement the standard
110    /// [`Deserializable`](miden_protocol::utils::serde::Deserializable) trait.
111    fn from(error: EncodingError) -> Self {
112        Self::InvalidValue(error.to_string())
113    }
114}
115
116impl From<EthAmountError> for EncodingError {
117    /// Re-spells the standards reducer's failures in this crate's error vocabulary, so a caller
118    /// of the encoding surface still handles one error type.
119    fn from(error: EthAmountError) -> Self {
120        match error {
121            EthAmountError::ScaleTooLarge => Self::ScaleExpTooLarge,
122            // both spellings of "wider than an asset amount can ever be": a quotient past the u64
123            // range, and a uint256 that overflowed before any scaling
124            EthAmountError::ScaledValueDoesNotFitU64 | EthAmountError::Overflow => {
125                Self::AmountTooLarge
126            },
127            EthAmountError::ScaledValueExceedsMaxFungibleAmount => Self::AmountOverCap,
128        }
129    }
130}