miden-objects 0.17.0-rc.4

Canonical Protobuf representations for Miden protocol objects
Documentation
use alloc::vec;

use assert_matches::assert_matches;
use miden_protocol::account::{
    AccountStorage,
    PartialStorage,
    PartialStorageMap,
    StorageMap,
    StorageMapKey,
    StorageSlot,
    StorageSlotName,
};
use miden_protocol::asset::{
    Asset,
    AssetId,
    AssetVault,
    FungibleAsset,
    NonFungibleAsset,
    PartialVault,
};
use miden_protocol::crypto::merkle::smt::{PartialSmt, Smt};
use miden_protocol::errors::{AccountError, AssetError, PartialAssetVaultError};
use miden_protocol::{Felt, Word};

use crate::decoded::account::test_utils::{dummy_account_id, partial_account};
use crate::test_utils::error_source;
use crate::{ConversionError, DecodeMessage, Verify, proto};

#[test]
fn partial_account_preserves_seed_validation_source() {
    let mut message = proto::account::PartialAccount::from(partial_account());
    message.seed = Some(Word::empty().into());

    let error = message
        .decode_fields()
        .unwrap()
        .verify()
        .map_err(ConversionError::new)
        .unwrap_err();

    assert_matches!(
        error_source::<AccountError>(&error),
        Some(AccountError::ExistingAccountWithSeed)
    );
}

#[test]
fn partial_account_rejects_new_account_without_seed() {
    let mut message = proto::account::PartialAccount::from(partial_account());
    message.nonce = Some(Felt::ZERO.into());

    let error = message
        .decode_fields()
        .unwrap()
        .verify()
        .map_err(ConversionError::new)
        .unwrap_err();

    assert_matches!(
        error_source::<AccountError>(&error),
        Some(AccountError::NewAccountMissingSeed)
    );
}

#[test]
fn partial_storage_rejects_duplicate_roots_before_collection() {
    let mut message = proto::account::PartialStorage::from(partial_account().storage());
    let map = proto::account::PartialStorageMap {
        smt: Some(PartialSmt::new(Word::from([9_u32, 0, 0, 0])).into()),
        keys: vec![],
    };
    message.maps = vec![map.clone(), map];

    let error = message
        .decode_fields()
        .unwrap()
        .verify()
        .map_err(ConversionError::new)
        .unwrap_err();

    assert_matches!(
        error_source::<crate::decoded::account::PartialStorageError>(&error),
        Some(crate::decoded::account::PartialStorageError::DuplicateRoot(root))
            if *root == Word::from([9_u32, 0, 0, 0])
    );
}

#[test]
fn partial_storage_preserves_root_not_in_header_source() {
    let mut message = proto::account::PartialStorage::from(partial_account().storage());
    message.maps.push(proto::account::PartialStorageMap {
        smt: Some(PartialSmt::new(Word::from([9_u32, 0, 0, 0])).into()),
        keys: vec![],
    });

    let error = message
        .decode_fields()
        .unwrap()
        .verify()
        .map_err(ConversionError::new)
        .unwrap_err();

    assert_matches!(
        error_source::<AccountError>(&error),
        Some(AccountError::StorageMapRootNotFound(root)) if *root == Word::from([9_u32, 0, 0, 0])
    );
}

#[test]
fn partial_storage_map_rejects_duplicate_raw_keys() {
    let key = StorageMapKey::from_index(1);
    let storage_map = StorageMap::with_entries([(key, Word::from([2_u32, 0, 0, 0]))]).unwrap();
    let mut message: proto::account::PartialStorageMap =
        PartialStorageMap::new_full(storage_map).into();
    message.keys.push(Word::from(key).into());

    let error = message
        .decode_fields()
        .unwrap()
        .verify()
        .map_err(ConversionError::new)
        .unwrap_err();

    assert_matches!(
        error_source::<miden_protocol::crypto::merkle::MerkleError>(&error),
        Some(miden_protocol::crypto::merkle::MerkleError::DuplicateValuesForIndex(_))
    );
}

#[test]
fn partial_storage_map_rejects_untracked_raw_keys() {
    let mut message: proto::account::PartialStorageMap =
        PartialStorageMap::new(Word::empty()).into();
    message.keys = vec![Word::from(StorageMapKey::from_index(1)).into()];

    let error = message
        .decode_fields()
        .unwrap()
        .verify()
        .map_err(ConversionError::new)
        .unwrap_err();

    assert_matches!(
        error_source::<miden_protocol::crypto::merkle::MerkleError>(&error),
        Some(miden_protocol::crypto::merkle::MerkleError::UntrackedKey(_))
    );
}

#[test]
fn partial_vault_rejects_duplicate_asset_ids() {
    let id = AssetId::new_fungible(dummy_account_id(9));
    let asset = Asset::new(id, Word::from([2_u32, 0, 0, 0])).unwrap();
    let mut message: proto::account::PartialVault =
        PartialVault::new_full(AssetVault::new(&[asset]).unwrap()).into();
    message.asset_ids.push(Word::from(id).into());

    let error = message
        .decode_fields()
        .unwrap()
        .verify()
        .map_err(ConversionError::new)
        .unwrap_err();

    assert_matches!(
        error_source::<PartialAssetVaultError>(&error),
        Some(PartialAssetVaultError::DuplicateAssetId(actual)) if *actual == id
    );
}

#[test]
fn partial_vault_preserves_invalid_asset_id_source() {
    let mut message: proto::account::PartialVault = PartialVault::new(Word::empty()).into();
    message.asset_ids = vec![Word::empty().into()];

    let error = message
        .decode_fields()
        .unwrap()
        .verify()
        .map_err(ConversionError::new)
        .unwrap_err();

    assert_matches!(error_source::<AssetError>(&error), Some(AssetError::UnknownAssetIdVersion(0)));
}

#[test]
fn partial_vault_preserves_invalid_asset_value_source() {
    let id = AssetId::new_fungible(dummy_account_id(9));
    let smt = Smt::with_entries([(id.hash().as_word(), Word::from([1_u32, 2, 0, 0]))]).unwrap();
    let message = proto::account::PartialVault {
        smt: Some(PartialSmt::from_proofs([smt.open(&id.hash().as_word())]).unwrap().into()),
        asset_ids: vec![Word::from(id).into()],
    };

    let error = message
        .decode_fields()
        .unwrap()
        .verify()
        .map_err(ConversionError::new)
        .unwrap_err();

    assert_matches!(
        error_source::<PartialAssetVaultError>(&error),
        Some(PartialAssetVaultError::InvalidAssetForId {
            source: AssetError::FungibleAssetValueMostSignificantElementsMustBeZero(_),
            ..
        })
    );
}

#[test]
fn partial_storage_decoding_normalizes_map_order() {
    let storage = AccountStorage::new(vec![
        StorageSlot::with_empty_map(StorageSlotName::mock(1)),
        StorageSlot::with_empty_map(StorageSlotName::mock(2)),
    ])
    .unwrap();
    let partial_storage = PartialStorage::new_full(storage);
    let expected = proto::account::PartialStorage::from(&partial_storage);
    let mut unordered = expected.clone();
    unordered.maps.reverse();

    let decoded = unordered.decode_fields().unwrap().verify().unwrap();

    assert_eq!(proto::account::PartialStorage::from(decoded), expected);
}

#[test]
fn partial_vault_decoding_normalizes_asset_id_order() {
    let vault =
        AssetVault::new(&[FungibleAsset::mock(2), NonFungibleAsset::mock(&[1, 2, 3])]).unwrap();
    let expected: proto::account::PartialVault = PartialVault::new_full(vault).into();
    let mut unordered = expected.clone();
    unordered.asset_ids.reverse();

    let decoded = unordered.decode_fields().unwrap().verify().unwrap();

    assert_eq!(proto::account::PartialVault::from(decoded), expected);
}