miden_objects/
errors.rs

1use alloc::{boxed::Box, string::String};
2use core::error::Error;
3
4use assembly::{diagnostics::reporting::PrintDiagnostic, Report};
5use miden_crypto::utils::HexParseError;
6use thiserror::Error;
7use vm_core::{mast::MastForestError, Felt, FieldElement};
8use vm_processor::DeserializationError;
9
10use super::{
11    account::AccountId,
12    asset::{FungibleAsset, NonFungibleAsset},
13    crypto::merkle::MerkleError,
14    note::NoteId,
15    Digest, Word, MAX_ACCOUNTS_PER_BLOCK, MAX_BATCHES_PER_BLOCK, MAX_INPUT_NOTES_PER_BLOCK,
16    MAX_OUTPUT_NOTES_PER_BATCH, MAX_OUTPUT_NOTES_PER_BLOCK,
17};
18use crate::{
19    account::{
20        AccountCode, AccountIdPrefix, AccountStorage, AccountType, PlaceholderType,
21        StoragePlaceholder,
22    },
23    block::BlockNumber,
24    note::{NoteAssets, NoteExecutionHint, NoteTag, NoteType, Nullifier},
25    ACCOUNT_UPDATE_MAX_SIZE, MAX_INPUTS_PER_NOTE, MAX_INPUT_NOTES_PER_TX, MAX_OUTPUT_NOTES_PER_TX,
26};
27
28// ACCOUNT COMPONENT TEMPLATE ERROR
29// ================================================================================================
30
31#[derive(Debug, Error)]
32pub enum AccountComponentTemplateError {
33    #[cfg(feature = "std")]
34    #[error("error trying to deserialize from toml")]
35    DeserializationError(#[source] toml::de::Error),
36    #[error("slot {0} is defined multiple times")]
37    DuplicateSlot(u8),
38    #[error("storage value was not of the expected type {0}")]
39    IncorrectStorageValue(String),
40    #[error("multi-slot entry should contain as many values as storage slots indices")]
41    MultiSlotArityMismatch,
42    #[error("error deserializing component metadata: {0}")]
43    MetadataDeserializationError(String),
44    #[error("component storage slots are not contiguous ({0} is followed by {1})")]
45    NonContiguousSlots(u8, u8),
46    #[error("storage value for placeholder `{0}` was not provided in the map")]
47    PlaceholderValueNotProvided(StoragePlaceholder),
48    #[error("storage map contains duplicate key `{0}`")]
49    StorageMapHasDuplicateKeys(String),
50    #[error("component storage slots have to start at 0, but they start at {0}")]
51    StorageSlotsDoNotStartAtZero(u8),
52    #[error(
53        "storage placeholder `{0}` appears more than once representing different types `{0}` and `{1}`"
54    )]
55    StoragePlaceholderTypeMismatch(StoragePlaceholder, PlaceholderType, PlaceholderType),
56}
57
58// ACCOUNT ERROR
59// ================================================================================================
60
61#[derive(Debug, Error)]
62pub enum AccountError {
63    #[error("failed to deserialize account code")]
64    AccountCodeDeserializationError(#[source] DeserializationError),
65    #[error("account code does not contain procedures but must contain at least one procedure")]
66    AccountCodeNoProcedures,
67    #[error("account code contains {0} procedures but it may contain at most {max} procedures", max = AccountCode::MAX_NUM_PROCEDURES)]
68    AccountCodeTooManyProcedures(usize),
69    #[error("account procedure {0}'s storage offset {1} does not fit into u8")]
70    AccountCodeProcedureStorageOffsetTooLarge(Digest, Felt),
71    #[error("account procedure {0}'s storage size {1} does not fit into u8")]
72    AccountCodeProcedureStorageSizeTooLarge(Digest, Felt),
73    #[error("account procedure {0}'s final two elements must be Felt::ZERO")]
74    AccountCodeProcedureInvalidPadding(Digest),
75    #[error("failed to assemble account component:\n{}", PrintDiagnostic::new(.0))]
76    AccountComponentAssemblyError(Report),
77    #[error("failed to merge components into one account code mast forest")]
78    AccountComponentMastForestMergeError(#[source] MastForestError),
79    #[error("procedure with MAST root {0} is present in multiple account components")]
80    AccountComponentDuplicateProcedureRoot(Digest),
81    #[error("failed to create account component")]
82    AccountComponentTemplateInstantiationError(#[source] AccountComponentTemplateError),
83    #[error("failed to update asset vault")]
84    AssetVaultUpdateError(#[source] AssetVaultError),
85    #[error("account build error: {0}")]
86    BuildError(String, #[source] Option<Box<AccountError>>),
87    #[error("faucet metadata decimals is {actual} which exceeds max value of {max}")]
88    FungibleFaucetTooManyDecimals { actual: u8, max: u8 },
89    #[error("faucet metadata max supply is {actual} which exceeds max value of {max}")]
90    FungibleFaucetMaxSupplyTooLarge { actual: u64, max: u64 },
91    #[error("account header data has length {actual} but it must be of length {expected}")]
92    HeaderDataIncorrectLength { actual: usize, expected: usize },
93    #[error("new account nonce {new} is less than the current nonce {current}")]
94    NonceNotMonotonicallyIncreasing { current: u64, new: u64 },
95    #[error("digest of the seed has {actual} trailing zeroes but must have at least {expected} trailing zeroes")]
96    SeedDigestTooFewTrailingZeros { expected: u32, actual: u32 },
97    #[error("storage slot at index {0} is not of type map")]
98    StorageSlotNotMap(u8),
99    #[error("storage slot at index {0} is not of type value")]
100    StorageSlotNotValue(u8),
101    #[error("storage slot index is {index} but the slots length is {slots_len}")]
102    StorageIndexOutOfBounds { slots_len: u8, index: u8 },
103    #[error("number of storage slots is {0} but max possible number is {max}", max = AccountStorage::MAX_NUM_STORAGE_SLOTS)]
104    StorageTooManySlots(u64),
105    #[error("procedure storage offset + size is {0} which exceeds the maximum value of {max}",
106      max = AccountStorage::MAX_NUM_STORAGE_SLOTS
107    )]
108    StorageOffsetPlusSizeOutOfBounds(u16),
109    #[error(
110        "procedure which does not access storage (storage size = 0) has non-zero storage offset"
111    )]
112    PureProcedureWithStorageOffset,
113    #[error("account component at index {component_index} is incompatible with account of type {account_type}")]
114    UnsupportedComponentForAccountType {
115        account_type: AccountType,
116        component_index: usize,
117    },
118    #[error("failed to parse account ID from final account header")]
119    FinalAccountHeaderIdParsingFailed(#[source] AccountIdError),
120    /// This variant can be used by methods that are not inherent to the account but want to return
121    /// this error type.
122    #[error("assumption violated: {0}")]
123    AssumptionViolated(String),
124}
125
126// ACCOUNT ID ERROR
127// ================================================================================================
128
129#[derive(Debug, Error)]
130pub enum AccountIdError {
131    #[error("failed to convert bytes into account ID prefix field element")]
132    AccountIdInvalidPrefixFieldElement(#[source] DeserializationError),
133    #[error("failed to convert bytes into account ID suffix field element")]
134    AccountIdInvalidSuffixFieldElement(#[source] DeserializationError),
135    #[error("`{0}` is not a known account storage mode")]
136    UnknownAccountStorageMode(Box<str>),
137    #[error(r#"`{0}` is not a known account type, expected one of "FungibleFaucet", "NonFungibleFaucet", "RegularAccountImmutableCode" or "RegularAccountUpdatableCode""#)]
138    UnknownAccountType(Box<str>),
139    #[error("failed to parse hex string into account ID")]
140    AccountIdHexParseError(#[source] HexParseError),
141    #[error("`{0}` is not a known account ID version")]
142    UnknownAccountIdVersion(u8),
143    #[error("anchor epoch in account ID must not be u16::MAX ({})", u16::MAX)]
144    AnchorEpochMustNotBeU16Max,
145    #[error("least significant byte of account ID suffix must be zero")]
146    AccountIdSuffixLeastSignificantByteMustBeZero,
147    #[error(
148        "anchor block must be an epoch block, that is, its block number must be a multiple of 2^{}",
149        BlockNumber::EPOCH_LENGTH_EXPONENT
150    )]
151    AnchorBlockMustBeEpochBlock,
152}
153
154// ACCOUNT DELTA ERROR
155// ================================================================================================
156
157#[derive(Debug, Error)]
158pub enum AccountDeltaError {
159    #[error("storage slot {0} was updated as a value and as a map")]
160    StorageSlotUsedAsDifferentTypes(u8),
161    #[error("non fungible vault can neither be added nor removed twice")]
162    DuplicateNonFungibleVaultUpdate(NonFungibleAsset),
163    #[error("fungible asset issued by faucet {faucet_id} has delta {delta} which overflows when added to current value {current}")]
164    FungibleAssetDeltaOverflow {
165        faucet_id: AccountId,
166        current: i64,
167        delta: i64,
168    },
169    #[error("account update of type `{left_update_type}` cannot be merged with account update of type `{right_update_type}`")]
170    IncompatibleAccountUpdates {
171        left_update_type: &'static str,
172        right_update_type: &'static str,
173    },
174    #[error("account delta could not be applied to account {account_id}")]
175    AccountDeltaApplicationFailed {
176        account_id: AccountId,
177        source: AccountError,
178    },
179    #[error("inconsistent nonce update: {0}")]
180    InconsistentNonceUpdate(String),
181    #[error("account ID {0} in fungible asset delta is not of type fungible faucet")]
182    NotAFungibleFaucetId(AccountId),
183}
184
185// ASSET ERROR
186// ================================================================================================
187
188#[derive(Debug, Error)]
189pub enum AssetError {
190    #[error(
191      "fungible asset amount {0} exceeds the max allowed amount of {max_amount}",
192      max_amount = FungibleAsset::MAX_AMOUNT
193    )]
194    FungibleAssetAmountTooBig(u64),
195    #[error("subtracting {subtrahend} from fungible asset amount {minuend} would overflow")]
196    FungibleAssetAmountNotSufficient { minuend: u64, subtrahend: u64 },
197    #[error("fungible asset word {hex} does not contain expected ZERO at word index 1",
198      hex = vm_core::utils::to_hex(Felt::elements_as_bytes(.0))
199    )]
200    FungibleAssetExpectedZero(Word),
201    #[error("cannot add fungible asset with issuer {other_issuer} to fungible asset with issuer {original_issuer}")]
202    FungibleAssetInconsistentFaucetIds {
203        original_issuer: AccountId,
204        other_issuer: AccountId,
205    },
206    #[error("faucet account ID in asset is invalid")]
207    InvalidFaucetAccountId(#[source] Box<dyn Error + Send + Sync + 'static>),
208    #[error(
209      "faucet id {0} of type {id_type} must be of type {expected_ty} for fungible assets",
210      id_type = .0.account_type(),
211      expected_ty = AccountType::FungibleFaucet
212    )]
213    FungibleFaucetIdTypeMismatch(AccountId),
214    #[error(
215      "faucet id {0} of type {id_type} must be of type {expected_ty} for non fungible assets",
216      id_type = .0.account_type(),
217      expected_ty = AccountType::NonFungibleFaucet
218    )]
219    NonFungibleFaucetIdTypeMismatch(AccountIdPrefix),
220    #[error("{0}")]
221    TokenSymbolError(String),
222}
223
224// ASSET VAULT ERROR
225// ================================================================================================
226
227#[derive(Debug, Error)]
228pub enum AssetVaultError {
229    #[error("adding fungible asset amounts would exceed maximum allowed amount")]
230    AddFungibleAssetBalanceError(#[source] AssetError),
231    #[error("provided assets contain duplicates")]
232    DuplicateAsset(#[source] MerkleError),
233    #[error("non fungible asset {0} already exists in the vault")]
234    DuplicateNonFungibleAsset(NonFungibleAsset),
235    #[error("fungible asset {0} does not exist in the vault")]
236    FungibleAssetNotFound(FungibleAsset),
237    #[error("faucet id {0} is not a fungible faucet id")]
238    NotAFungibleFaucetId(AccountId),
239    #[error("non fungible asset {0} does not exist in the vault")]
240    NonFungibleAssetNotFound(NonFungibleAsset),
241    #[error("subtracting fungible asset amounts would underflow")]
242    SubtractFungibleAssetBalanceError(#[source] AssetError),
243}
244
245// NOTE ERROR
246// ================================================================================================
247
248#[derive(Debug, Error)]
249pub enum NoteError {
250    #[error("duplicate fungible asset from issuer {0} in note")]
251    DuplicateFungibleAsset(AccountId),
252    #[error("duplicate non fungible asset {0} in note")]
253    DuplicateNonFungibleAsset(NonFungibleAsset),
254    #[error("note type {0:?} is inconsistent with note tag {1}")]
255    InconsistentNoteTag(NoteType, u64),
256    #[error("adding fungible asset amounts would exceed maximum allowed amount")]
257    AddFungibleAssetBalanceError(#[source] AssetError),
258    #[error("note sender is not a valid account ID")]
259    NoteSenderInvalidAccountId(#[source] AccountIdError),
260    #[error("note tag use case {0} must be less than 2^{exp}", exp = NoteTag::MAX_USE_CASE_ID_EXPONENT)]
261    NoteTagUseCaseTooLarge(u16),
262    #[error(
263        "note execution hint tag {0} must be in range {from}..={to}",
264        from = NoteExecutionHint::NONE_TAG,
265        to = NoteExecutionHint::ON_BLOCK_SLOT_TAG,
266    )]
267    NoteExecutionHintTagOutOfRange(u8),
268    #[error("note execution hint after block variant cannot contain u32::MAX")]
269    NoteExecutionHintAfterBlockCannotBeU32Max,
270    #[error("invalid note execution hint payload {1} for tag {0}")]
271    InvalidNoteExecutionHintPayload(u8, u32),
272    #[error("note type {0:b} does not match any of the valid note types {public}, {private} or {encrypted}",
273      public = NoteType::Public as u8,
274      private = NoteType::Private as u8,
275      encrypted = NoteType::Encrypted as u8,
276    )]
277    InvalidNoteType(u64),
278    #[error("note location index {node_index_in_block} is out of bounds 0..={highest_index}")]
279    NoteLocationIndexOutOfBounds {
280        node_index_in_block: u16,
281        highest_index: usize,
282    },
283    #[error("note network execution requires account stored on chain")]
284    NetworkExecutionRequiresOnChainAccount,
285    #[error("note network execution requires a public note but note is of type {0:?}")]
286    NetworkExecutionRequiresPublicNote(NoteType),
287    #[error("failed to assemble note script:\n{}", PrintDiagnostic::new(.0))]
288    NoteScriptAssemblyError(Report),
289    #[error("failed to deserialize note script")]
290    NoteScriptDeserializationError(#[source] DeserializationError),
291    #[error("public use case requires a public note but note is of type {0:?}")]
292    PublicUseCaseRequiresPublicNote(NoteType),
293    #[error("note contains {0} assets which exceeds the maximum of {max}", max = NoteAssets::MAX_NUM_ASSETS)]
294    TooManyAssets(usize),
295    #[error("note contains {0} inputs which exceeds the maximum of {max}", max = MAX_INPUTS_PER_NOTE)]
296    TooManyInputs(usize),
297}
298
299// CHAIN MMR ERROR
300// ================================================================================================
301
302#[derive(Debug, Error)]
303pub enum ChainMmrError {
304    #[error("block num {block_num} exceeds chain length {chain_length} implied by the chain MMR")]
305    BlockNumTooBig {
306        chain_length: usize,
307        block_num: BlockNumber,
308    },
309    #[error("duplicate block {block_num} in chain MMR")]
310    DuplicateBlock { block_num: BlockNumber },
311    #[error("chain MMR does not track authentication paths for block {block_num}")]
312    UntrackedBlock { block_num: BlockNumber },
313}
314
315impl ChainMmrError {
316    pub fn block_num_too_big(chain_length: usize, block_num: BlockNumber) -> Self {
317        Self::BlockNumTooBig { chain_length, block_num }
318    }
319
320    pub fn duplicate_block(block_num: BlockNumber) -> Self {
321        Self::DuplicateBlock { block_num }
322    }
323
324    pub fn untracked_block(block_num: BlockNumber) -> Self {
325        Self::UntrackedBlock { block_num }
326    }
327}
328
329// TRANSACTION SCRIPT ERROR
330// ================================================================================================
331
332#[derive(Debug, Error)]
333pub enum TransactionScriptError {
334    #[error("failed to assemble transaction script:\n{}", PrintDiagnostic::new(.0))]
335    AssemblyError(Report),
336}
337
338// TRANSACTION INPUT ERROR
339// ================================================================================================
340
341#[derive(Debug, Error)]
342pub enum TransactionInputError {
343    #[error("account seed must be provided for new accounts")]
344    AccountSeedNotProvidedForNewAccount,
345    #[error("account seed must not be provided for existing accounts")]
346    AccountSeedProvidedForExistingAccount,
347    #[error(
348      "anchor block header for epoch {0} (block number = {block_number}) must be provided in the chain mmr for the new account",
349      block_number = BlockNumber::from_epoch(*.0),
350    )]
351    AnchorBlockHeaderNotProvidedForNewAccount(u16),
352    #[error("transaction input note with nullifier {0} is a duplicate")]
353    DuplicateInputNote(Nullifier),
354    #[error("ID {expected} of the new account does not match the ID {actual} computed from the provided seed")]
355    InconsistentAccountSeed { expected: AccountId, actual: AccountId },
356    #[error("chain mmr has length {actual} which does not match block number {expected} ")]
357    InconsistentChainLength {
358        expected: BlockNumber,
359        actual: BlockNumber,
360    },
361    #[error("chain mmr has root {actual} which does not match block header's root {expected}")]
362    InconsistentChainRoot { expected: Digest, actual: Digest },
363    #[error("block in which input note with id {0} was created is not in chain mmr")]
364    InputNoteBlockNotInChainMmr(NoteId),
365    #[error("input note with id {0} was not created in block {1}")]
366    InputNoteNotInBlock(NoteId, BlockNumber),
367    #[error("account ID computed from seed is invalid")]
368    InvalidAccountIdSeed(#[source] AccountIdError),
369    #[error(
370        "total number of input notes is {0} which exceeds the maximum of {MAX_INPUT_NOTES_PER_TX}"
371    )]
372    TooManyInputNotes(usize),
373}
374
375// TRANSACTION OUTPUT ERROR
376// ===============================================================================================
377
378#[derive(Debug, Error)]
379pub enum TransactionOutputError {
380    #[error("transaction output note with id {0} is a duplicate")]
381    DuplicateOutputNote(NoteId),
382    #[error("final account hash is not in the advice map")]
383    FinalAccountHashMissingInAdviceMap,
384    #[error("failed to parse final account header")]
385    FinalAccountHeaderParseFailure(#[source] AccountError),
386    #[error("output notes commitment {expected} from kernel does not match computed commitment {actual}")]
387    OutputNotesCommitmentInconsistent { expected: Digest, actual: Digest },
388    #[error("transaction kernel output stack is invalid: {0}")]
389    OutputStackInvalid(String),
390    #[error("total number of output notes is {0} which exceeds the maximum of {MAX_OUTPUT_NOTES_PER_TX}")]
391    TooManyOutputNotes(usize),
392}
393
394// PROVEN TRANSACTION ERROR
395// ================================================================================================
396
397#[derive(Debug, Error)]
398pub enum ProvenTransactionError {
399    #[error("proven transaction's final account hash {tx_final_hash} and account details hash {details_hash} must match")]
400    AccountFinalHashMismatch {
401        tx_final_hash: Digest,
402        details_hash: Digest,
403    },
404    #[error("proven transaction's final account ID {tx_account_id} and account details id {details_account_id} must match")]
405    AccountIdMismatch {
406        tx_account_id: AccountId,
407        details_account_id: AccountId,
408    },
409    #[error("failed to construct input notes for proven transaction")]
410    InputNotesError(TransactionInputError),
411    #[error("off-chain account {0} should not have account details")]
412    OffChainAccountWithDetails(AccountId),
413    #[error("on-chain account {0} is missing its account details")]
414    OnChainAccountMissingDetails(AccountId),
415    #[error("new on-chain account {0} is missing its account details")]
416    NewOnChainAccountRequiresFullDetails(AccountId),
417    #[error(
418        "existing on-chain account {0} should only provide delta updates instead of full details"
419    )]
420    ExistingOnChainAccountRequiresDeltaDetails(AccountId),
421    #[error("failed to construct output notes for proven transaction")]
422    OutputNotesError(TransactionOutputError),
423    #[error(
424      "account update of size {update_size} for account {account_id} exceeds maximum update size of {ACCOUNT_UPDATE_MAX_SIZE}",
425    )]
426    AccountUpdateSizeLimitExceeded {
427        account_id: AccountId,
428        update_size: usize,
429    },
430}
431
432// BLOCK VALIDATION ERROR
433// ================================================================================================
434
435#[derive(Debug, Error)]
436pub enum BlockError {
437    #[error("duplicate note with id {0} in the block")]
438    DuplicateNoteFound(NoteId),
439    #[error("too many accounts updated in the block (max: {MAX_ACCOUNTS_PER_BLOCK}, actual: {0})")]
440    TooManyAccountUpdates(usize),
441    #[error("too many notes in the batch (max: {MAX_OUTPUT_NOTES_PER_BATCH}, actual: {0})")]
442    TooManyNotesInBatch(usize),
443    #[error("too many notes in the block (max: {MAX_OUTPUT_NOTES_PER_BLOCK}, actual: {0})")]
444    TooManyNotesInBlock(usize),
445    #[error("too many nullifiers in the block (max: {MAX_INPUT_NOTES_PER_BLOCK}, actual: {0})")]
446    TooManyNullifiersInBlock(usize),
447    #[error(
448        "too many transaction batches in the block (max: {MAX_BATCHES_PER_BLOCK}, actual: {0})"
449    )]
450    TooManyTransactionBatches(usize),
451}